Skip to content

Commit

Permalink
docs: update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
FlickerSoul committed Nov 29, 2023
1 parent b196cfe commit 87838e8
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 79 deletions.
18 changes: 18 additions & 0 deletions docs/API/useful_types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Useful Types

## API
::: gapper.core.types
options:
members:
- CustomTestFn
- CustomTestData
- CustomEqualityCheckFn
- CustomEqualityTestData
- PreHookFn
- PreHookData
- PostHookFn
- PostHookData
- PreTestsFn
- PreTestsData
- PostTestsFn
- PostTestsData
11 changes: 0 additions & 11 deletions docs/API/utils.md

This file was deleted.

28 changes: 14 additions & 14 deletions docs/Tutorials/Detailed-Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ gap_weight: float | Sequence[float] | None = None,
We will dedicate a page to discuss their usages. [gap_ Keywords](gap_-Keywords.md)


### Before and After All The Tests
### Run Something Before and After All The Tests

You can add `@pre_tests` and `@post_tests` decorators anywhere above the `@problem` decorator. These decorators
help you setup functions run before and after __all__ the tests respectively. Suppose you want to setup some files used in testing,
Expand All @@ -467,14 +467,14 @@ def create_files(num_of_files: int, directory: Path) -> None:
f.write("hello world")
files.append(path)

def pre_test_hook(*_) -> None:
def pre_test_hook(_) -> None:
global tmp_dir
with TemporaryDirectory(delete=False) as temporary:
tmp_dir = Path(tmp_dir)
create_files(10, tmp_dir)


def post_test_hook(*args) -> None:
def post_test_hook(_) -> None:
global tmp_dir
assert tmp_dir is not None
tmp_dir.rmdir()
Expand Down Expand Up @@ -507,7 +507,7 @@ def create_files(num_of_files: int, directory: Path) -> None:
...


def pre_test_hook(*_) -> None:
def pre_test_hook(_) -> None:
with TemporaryDirectory() as tmp_dir:
create_files(10, Path(tmp_dir))

Expand Down Expand Up @@ -558,7 +558,7 @@ def randomly_generate_numbers(times: int) -> Generator[param, None, None]:
for _ in range(times):
yield param([random.randint(0, 100) for _ in range(random.randint(0, 100))])

@test_cases.param_iterm(randomly_generate_numbers(10), gap_max_score=1) # the first two lines have the same semantics, which is creating
@test_cases.param_iter(randomly_generate_numbers(10), gap_max_score=1) # the first two lines have the same semantics, which is creating
@test_cases.params(*randomly_generate_numbers(10), gap_max_score=1) # 10 random generated numbers, each worth 1 point
@test_cases.params(param([1, 2]), param([3, 4], gap_max_score=2)) # `param` is a helper that allows you to specify parameters, in a more
@test_cases.params([[5, 6]], [[7, 8]], gap_hidden=[True, False]) # readable way. This problem has 6 test cases, where the parameters
Expand All @@ -584,10 +584,11 @@ This is how you can override the equality check between the solution and the sub

```python
from gapper import problem, test_cases, test_case
from gapper.core.types import CustomEqualityTestData
from typing import Iterable

def override_check(solution_ans, submission_ans) -> bool:
return set(solution_ans) == set(submission_ans)
def override_check(data: CustomEqualityTestData) -> None:
assert set(data.expected) == set(data.actual)

@test_cases(11, 12, 13, gap_override_check=override_check)
@test_case(10, gap_override_check=override_check)
Expand All @@ -600,16 +601,15 @@ This is how you can override how the submission should be tested.

```python
from gapper import problem, test_case, test_cases
from gapper.core.unittest_wrapper import TestCaseWrapper
from gapper.core.test_result import TestResult
from gapper.core.types import CustomTestData


def override_test(tc: TestCaseWrapper, result: TestResult, solution, submission):
solution_answer = solution(*tc.test_param.args)
student_answer = submission(*tc.test_param.args)
tc.assertEqual(solution_answer, student_answer)
def override_test(data: CustomTestData):
solution_answer = data.solution(*data.case.test_param.args)
student_answer = data.submission(*data.case.test_param.args)
data.case.assertEqual(solution_answer, student_answer)

result.set_pass_status("failed")
data.result_proxy.set_pass_status("failed")


@test_cases([3, 4], [5, 6], gap_override_test=override_test)
Expand Down
13 changes: 6 additions & 7 deletions docs/Tutorials/Easy-Context.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ function `custom_test` that uses the captured context, and the problem solution

```python
from gapper import problem, test_case, test_cases
from gapper.core.unittest_wrapper import TestCaseWrapper
from gapper.core.test_result import TestResult
from gapper.core.types import CustomTestData

from typing import Callable

Expand All @@ -32,15 +31,15 @@ adder: Callable[[int, int], int]


# custom test that uses the captured context
def custom_test(case: TestCaseWrapper, result_proxy: TestResult, solution, submission):
assert my_adder(*case.test_param.args) == adder(*case.test_param.args) # notice here
def custom_test(data: CustomTestData):
assert my_adder(*data.args) == adder(*data.args) # notice here
# adder is not defined, but we can use it
# this is because it will be captured from students' submission context

# test if student's adder behaves the same in the solution as in their submission
assert solution(*case.test_param.args, adder) == submission(
*case.test_param.args,
case.context.adder, # case.context.adder is the same as adder
assert data.solution(*data.args, adder) == data.submission(
*data.args,
data.case.context.adder, # case.context.adder is the same as adder
)


Expand Down
37 changes: 31 additions & 6 deletions docs/Tutorials/Various-Function-Protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,44 @@
It can be confusing to remember all function protocols used in gapper. Below, we list the function signatures and their docstrings for each use case.

## `gap_override_test`
::: gapper.core.utils.CustomTestFn
::: gapper.core.types
options:
members:
- CustomTestFn
- CustomTestData

## `gap_override_check`
::: gapper.core.utils.CustomEqualityCheckFn
::: gapper.core.types
options:
members:
- CustomEqualityCheckFn
- CustomEqualityTestData

## `gap_pre_hook`
::: gapper.core.utils.PreHookFn
::: gapper.core.types
options:
members:
- PreHookFn
- PreHookData

## `gap_post_hook`
::: gapper.core.utils.PostHookFn
::: gapper.core.types
options:
members:
- PostHookFn
- PostHookData

## `pre_tests`
::: gapper.core.utils.PreTestsFn
::: gapper.core.types
options:
members:
- PreTestsFn
- PreTestsData


## `post_tests`
::: gapper.core.utils.PostTestsFn
::: gapper.core.types
options:
members:
- PostTestsFn
- PostTestsData
86 changes: 46 additions & 40 deletions docs/Tutorials/gap_-Keywords.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,18 @@ will result in 5 extra points when the student passes the test, shown as followi

## `gap_override_check`

You can override tests' equality checks by passing a comparator function to `gap_override_check` keyword.
You can override tests' equality checks by passing a comparator function to `gap_override_check` keyword. The
function should raise an `AssertionError` if the two values are not equal.

For example, suppose the you want to compare answers from students' submissions with the solution but do not care about ordering, you can pass
For example, suppose that you want to compare answers from students' submissions with the solution but do not care about ordering, you can pass
`gap_override_check=set_equality` to `@test_case()` where `set_equality` is pre-defined in your script as

```python
def set_equality(solution_answer: Any, submission_answer: Any) -> bool:
return set(solution_answer) == set(submission_answer)
from gapper.core.types import CustomEqualityTestData


def set_equality(data: CustomEqualityTestData) -> None:
assert set(data.expected) == set(data.actual)
```

## `gap_override_test`
Expand All @@ -182,8 +186,7 @@ You can override entire test by passing a custom function to `gap_override_test`
import ast
import inspect
from gapper import problem, test_case
from gapper.core.test_result import TestResult
from gapper.core.unittest_wrapper import TestCaseWrapper
from gapper.core.types import CustomTestData

from pytest import approx

Expand All @@ -198,11 +201,11 @@ def check_recursive_ast(fn):
return False


def custom_test(param: TestCaseWrapper, result_proxy: TestResult, solution, submission) -> bool:
soln_ans = solution(*param.args, **param.kwargs)
subm_ans = submission(*param.args, **param.kwargs)
def custom_test(data: CustomTestData) -> None:
soln_ans = data.solution(*data.args, **data.kwargs)
subm_ans = data.submission(*data.args, **data.kwargs)

param.assertEqual(soln_ans, subm_ans) # equivalent to `assert soln_ans == subm_ans`
data.case.assertEqual(soln_ans, subm_ans) # equivalent to `assert soln_ans == subm_ans`

# param.assertTrue(check_recursive_ast(submission))
# equivalent to `assert check_recursive_ast(submission)`
Expand All @@ -211,8 +214,8 @@ def custom_test(param: TestCaseWrapper, result_proxy: TestResult, solution, subm
# the following line is dumb but just for demonstration
assert soln_ans == approx(subm_ans, rel=1e-3)

if not check_recursive_ast(submission):
result_proxy.set_score(result_proxy.max_score // 2)
if not check_recursive_ast(data.submission):
data.result_proxy.set_score(data.result_proxy.max_score // 2)


@test_case(10, gap_override_test=custom_test)
Expand All @@ -224,9 +227,10 @@ def fib(n: int) -> int:
A overriding function show have the following positional parameter signature

```python
from gapper.core.types import CustomTestData

class CustomTestFn(Protocol):
def __call__[T](self, param: TestCaseWrapper, result_proxy: TestResult, expected: T, actual: T) -> None:
def __call__[T](self, data: CustomTestData[T]) -> None:
...
```

Expand All @@ -238,19 +242,18 @@ You can setup the environment or use it to modify the test case before. For exam

```python
from gapper import problem, test_case
from gapper.core.unittest_wrapper import TestCaseWrapper
from gapper.core.test_result import TestResult
from gapper.core.types import PreHookData
from tempfile import NamedTemporaryFile


def preparation(param: TestCaseWrapper, result_proxy: TestResult, submission, solution) -> None:
lines = param.test_param.args[0]
def preparation(data: PreHookData) -> None:
lines = data.args[0]
# put lines into a temporary file
with NamedTemporaryFile("w", delete=False) as infile:
infile.write("\n".join(lines))

# set the args to the filename passed the student's function
param.test_param.args = (infile.name,)
data.param.args = (infile.name,)


def gen_lines(num_of_lines: int) -> list[str]:
Expand All @@ -270,16 +273,16 @@ the function `preparation` can be rewritten as

```python
from typing import Generator
from gapper import TestCaseWrapper, TestResult
from tempfile import NamedTemporaryFile
from gapper.core.types import PreHookData


def preparation(param: TestCaseWrapper, result_proxy: TestResult, submission, solution) -> Generator[None, None, None]:
lines = param.test_param.args[0]
def preparation(data: PreHookData) -> Generator[None, None, None]:
lines = data.args[0]
# put lines into a temporary file
with NamedTemporaryFile("w") as infile:
infile.write("\n".join(lines))
param.test_param.args = (infile.name,)
data.param.args = (infile.name,)

# everything above yield will be run before the test case is tested
yield
Expand All @@ -289,6 +292,17 @@ def preparation(param: TestCaseWrapper, result_proxy: TestResult, submission, so
```


A pre hook function has to follow the following positional parameter signature

```python
from gapper.core.types import PreHookData

class PostHookFn(Protocol):
def __call__[T](self, data: PreHookData[T]) -> None:
...
```


## `gap_post_hooks`

`gap_post_hooks` are function(s) run after running the student's answer and the solution, and comparing the results and stdout of the two.
Expand All @@ -297,8 +311,7 @@ Consider the situation in which you'd like to provide extra checks but not overr

```python
from gapper import problem, test_case
from gapper.core.test_result import TestResult
from gapper.core.unittest_wrapper import TestCaseWrapper
from gapper.core.types import PostHookData

import ast
import inspect
Expand All @@ -314,12 +327,11 @@ def check_recursive_ast(fn):
return False


def recursive_check(param: TestCaseWrapper, result_proxy: TestResult, solution, submission,
sln_results: Tuple[Any, str | None], sub_results: Tuple[Any, str | None]) -> None:
if not check_recursive_ast(submission):
result_proxy.set_score(result_proxy.max_score // 2)
result_proxy.set_pass_status("failed")
result_proxy.add_description(
def recursive_check(data: PostHookData) -> None:
if not check_recursive_ast(data.submission):
data.result_proxy.set_score(data.result_proxy.max_score // 2)
data.result_proxy.set_pass_status("failed")
data.result_proxy.add_description(
"Failed because recursive call not found in submission."
)

Expand All @@ -330,19 +342,13 @@ def fib(n: int) -> int:
...
```

A post check function has to follow the following positional parameter signature
A post hook function has to follow the following positional parameter signature

```python
from gapper.core.types import PostHookData

class PostHookFn(Protocol):
def __call__[T](
self,
param: TestCaseWrapper,
result_proxy: TestResult,
solution: T,
submission: T,
expected_results: Tuple[Any, str | None],
actual_results: Tuple[Any, str | None],
) -> None:
def __call__[T](self, data: PostHookData[T]) -> None:
...
```

Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ nav:
- API/problem_extras.md
- API/test_cases.md
- API/test_result.md
- API/utils.md
- API/useful_types.md
- API/pipeline_support.md
- API/test_case_wrapper.md
- API/result_synthesizer.md
Expand Down

0 comments on commit 87838e8

Please sign in to comment.