Skip to content

Commit

Permalink
Merge pull request #437 from dodona-edu/feature/tab-merge
Browse files Browse the repository at this point in the history
Rework execution
  • Loading branch information
niknetniko authored Sep 20, 2023
2 parents 31035b0 + 1bbf36a commit 5bc31ff
Show file tree
Hide file tree
Showing 68 changed files with 1,508 additions and 1,633 deletions.
55 changes: 46 additions & 9 deletions tested/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"""
import logging
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Dict, Optional, Tuple
from typing import IO, TYPE_CHECKING, Any, Optional

from attrs import define, evolve, field

from tested.parsing import fallback_field, get_converter
from tested.testsuite import ExecutionMode, Suite, SupportedLanguage
from tested.utils import consume_shebang, get_identifier, smart_close
from tested.utils import get_identifier, smart_close

# Prevent circular imports
if TYPE_CHECKING:
Expand Down Expand Up @@ -41,7 +41,7 @@ class Options:
fails. If nothing is given, the language-dependent default is used. If a boolean
is given, this value is used, regardless of the language default.
"""
language: Dict[str, Dict[str, Any]] = field(factory=dict)
language: dict[str, dict[str, Any]] = field(factory=dict)
"""
Language-specific options for the judge. These depend on the language
implementation; the judge itself does nothing with it.
Expand Down Expand Up @@ -87,7 +87,7 @@ class DodonaConfig:
# Sometimes, we need to offset the source code.
source_offset: int = 0

def config_for(self) -> Dict[str, Any]:
def config_for(self) -> dict[str, Any]:
return self.options.language.get(self.programming_language, dict())

def linter(self) -> bool:
Expand Down Expand Up @@ -125,7 +125,7 @@ def options(self) -> Options:
class Bundle:
"""A bundle of arguments and configs for running everything."""

lang_config: "Language"
language: "Language"
global_config: GlobalConfig
out: IO

Expand All @@ -146,10 +146,47 @@ def context_separator_secret(self) -> str:
return self.global_config.context_separator_secret


def _get_language(config: DodonaConfig) -> Tuple[SupportedLanguage, int]:
def _consume_shebang(submission: Path) -> Optional["SupportedLanguage"]:
"""
Find the shebang in the submission, and if it is present, consume it.
:param submission: The path to the file containing the code.
:return: The programming language if found.
"""
from tested.testsuite import SupportedLanguage

language = None
try:
with open(submission, "r+") as file:
lines = file.readlines()
file.seek(0)

# Steps to find
has_potential = True
for line in lines:
stripped = line.strip()
if has_potential and stripped.startswith("#!tested"):
try:
_, language = stripped.split(" ")
except ValueError:
_logger.error(f"Invalid shebang on line {stripped}")
else:
file.write(line)
if has_potential and stripped:
has_potential = False
file.truncate()
except FileNotFoundError:
# Nothing we can do if there is no submission.
pass

return SupportedLanguage(language) if language else None


def _get_language(config: DodonaConfig) -> tuple[SupportedLanguage, int]:
import tested.languages as langs

bang = consume_shebang(config.source)
bang = _consume_shebang(config.source)
if bang and langs.language_exists(bang):
_logger.debug(
f"Found shebang language and it exists, using {bang} instead "
Expand All @@ -168,7 +205,7 @@ def create_bundle(
config: DodonaConfig,
output: IO,
suite: Suite,
language: Optional[str] = None,
language: str | None = None,
) -> Bundle:
"""
Create a configuration bundle.
Expand All @@ -194,4 +231,4 @@ def create_bundle(
suite=suite,
)
lang_config = langs.get_language(global_config, language)
return Bundle(lang_config=lang_config, global_config=global_config, out=output)
return Bundle(language=lang_config, global_config=global_config, out=output)
22 changes: 10 additions & 12 deletions tested/datatypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
Additionally, the types in this file are organized by their JSON encoding type.
They are also split in "basic types" and "advanced types".
"""
from typing import FrozenSet, Set, Tuple, Union

from tested.datatypes.advanced import (
AdvancedNothingTypes,
AdvancedNumericTypes,
Expand All @@ -33,23 +31,23 @@
from tested.datatypes.complex import ComplexExpressionTypes
from tested.utils import get_args

NumericTypes = Union[BasicNumericTypes, AdvancedNumericTypes]
StringTypes = Union[BasicStringTypes, AdvancedStringTypes]
NumericTypes = BasicNumericTypes | AdvancedNumericTypes
StringTypes = BasicStringTypes | AdvancedStringTypes
BooleanTypes = BasicBooleanTypes
NothingTypes = Union[BasicNothingTypes, AdvancedNothingTypes]
SequenceTypes = Union[BasicSequenceTypes, AdvancedSequenceTypes]
NothingTypes = BasicNothingTypes | AdvancedNothingTypes
SequenceTypes = BasicSequenceTypes | AdvancedSequenceTypes
ObjectTypes = BasicObjectTypes

SimpleTypes = Union[NumericTypes, StringTypes, BooleanTypes, NothingTypes]
ComplexTypes = Union[SequenceTypes, ObjectTypes]
SimpleTypes = NumericTypes | StringTypes | BooleanTypes | NothingTypes
ComplexTypes = SequenceTypes | ObjectTypes

AllTypes = Union[BasicTypes, AdvancedTypes]
AllTypes = BasicTypes | AdvancedTypes

ExpressionTypes = Union[AllTypes, ComplexExpressionTypes]
NestedTypes = Set[Tuple[ExpressionTypes, FrozenSet[ExpressionTypes]]]
ExpressionTypes = AllTypes | ComplexExpressionTypes
NestedTypes = set[tuple[ExpressionTypes, frozenset[ExpressionTypes]]]

# Test that our aliases are correct.
assert set(get_args(AllTypes)) == set(get_args(Union[SimpleTypes, ComplexTypes]))
assert set(get_args(AllTypes)) == set(get_args(SimpleTypes | ComplexTypes))


def resolve_to_basic(type_: AllTypes) -> BasicTypes:
Expand Down
2 changes: 1 addition & 1 deletion tested/descriptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def process_problem_statement(

bundle = Bundle(
global_config=global_config,
lang_config=language,
language=language,
out=open(os.devnull, "w"),
)

Expand Down
2 changes: 1 addition & 1 deletion tested/descriptions/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def convert_templated_problem(bundle: Bundle, raw_description: str) -> str:
description_template = Template(
source=raw_description, autoescape=False, keep_trailing_newline=True
)
language = bundle.lang_config
language = bundle.language
set_locale(bundle.config.natural_language)
return description_template.render(
# Conventionalize functions
Expand Down
4 changes: 2 additions & 2 deletions tested/descriptions/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def render_one_statement(bundle: Bundle, statement: str) -> str:
parsed_string = parse_string(statement)
generated_statement = generate_statement(bundle, parsed_string)
# Allow the language to modify the template a bit.
return bundle.lang_config.cleanup_description(generated_statement)
return bundle.language.cleanup_description(generated_statement)


class TestedRenderer(MarkdownRenderer):
Expand All @@ -40,7 +40,7 @@ def _render_doctest(self, element: block.FencedCode) -> str:
doctests = self._doctest_parser.get_examples(raw_code)

resulting_lines = []
prompt = self.bundle.lang_config.get_declaration_metadata().get("prompt", ">")
prompt = self.bundle.language.get_declaration_metadata().get("prompt", ">")

# Both the doctests and the results are parsed as values in the DSL.
for examples in doctests:
Expand Down
Loading

0 comments on commit 5bc31ff

Please sign in to comment.