-
Notifications
You must be signed in to change notification settings - Fork 4
Abstract syntax tree + forbidden keyword test #300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
9af855d
feat: add StructuralAnalysisStep to the pipeline
jaoppb 7fd28cb
feat: implement ForbiddenKeywordTest using ast-grep for structural an…
jaoppb 34d639e
chore: removed invalid docker compose volume
jaoppb 8ea863b
docs: document structural analysis step and forbidden keyword test
jaoppb 9af945d
Merge branch 'main' into abstract-syntax-tree
ArthurCRodrigues File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| from dataclasses import dataclass | ||
| from typing import Dict, Optional, TYPE_CHECKING | ||
|
|
||
| if TYPE_CHECKING: | ||
| from ast_grep_py import SgRoot | ||
|
|
||
| @dataclass | ||
| class StructuralAnalysisResult: | ||
| """ | ||
| Holds the results of structural analysis for a submission. | ||
|
|
||
| Attributes: | ||
| roots: A dictionary mapping filenames to their corresponding ast-grep root nodes. | ||
| If a file could not be parsed, the value is None. | ||
| """ | ||
| roots: Dict[str, Optional['SgRoot']] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,8 @@ | ||||||
| import logging | ||||||
| from typing import List, Optional | ||||||
|
|
||||||
| from pydantic import ValidationError | ||||||
|
|
||||||
| from autograder.models.abstract.template import Template | ||||||
| from autograder.models.abstract.test_function import TestFunction | ||||||
| from autograder.models.config.category import CategoryConfig | ||||||
|
|
@@ -99,17 +101,26 @@ def __parse_test(self, config: TestConfig) -> TestNode: | |||||
| raise ValueError(f"Couldn't find test function '{function_name}'") | ||||||
|
|
||||||
| file_target = [config.file] if config.file else None | ||||||
| test_params = config.get_kwargs_dict() or {} | ||||||
|
|
||||||
| # Perform early validation if the test function provides a schema. | ||||||
| if test_function.config_schema: | ||||||
| try: | ||||||
| # We use model_validate to leverage Pydantic's validation logic. | ||||||
| test_function.config_schema(**test_params) | ||||||
|
||||||
| test_function.config_schema(**test_params) | |
| test_function.config_schema.model_validate(test_params) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import logging | ||
| from typing import Dict, Optional | ||
|
|
||
| from autograder.models.abstract.step import Step | ||
| from autograder.models.pipeline_execution import PipelineExecution | ||
| from autograder.models.dataclass.step_result import StepResult, StepName | ||
| from autograder.models.dataclass.structural_analysis_result import StructuralAnalysisResult | ||
| from sandbox_manager.models.sandbox_models import Language | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| try: | ||
| from ast_grep_py import SgRoot | ||
| except ImportError: | ||
| SgRoot = None | ||
|
|
||
| class StructuralAnalysisStep(Step): | ||
| """ | ||
| Parses submission files into ast-grep SgRoot objects. | ||
| This enables structural pattern matching in subsequent grading steps. | ||
| """ | ||
|
|
||
| @property | ||
| def step_name(self) -> StepName: | ||
| return StepName.STRUCTURAL_ANALYSIS | ||
|
|
||
| def _execute(self, pipeline_exec: PipelineExecution) -> PipelineExecution: | ||
| submission = pipeline_exec.submission | ||
| language = submission.language | ||
|
|
||
| if not language: | ||
| logger.warning("No language specified for submission; skipping structural analysis.") | ||
| return pipeline_exec.add_step_result(StepResult.success(self.step_name, StructuralAnalysisResult(roots={}))) | ||
|
|
||
| if SgRoot is None: | ||
| logger.error("ast-grep-py is not installed; structural analysis will be skipped.") | ||
| return pipeline_exec.add_step_result(StepResult.fail(self.step_name, "ast-grep-py not installed")) | ||
|
jaoppb marked this conversation as resolved.
|
||
|
|
||
| ast_grep_lang = self._map_language(language) | ||
| if not ast_grep_lang: | ||
| logger.warning(f"Language {language.value} is not supported by ast-grep; skipping.") | ||
| return pipeline_exec.add_step_result(StepResult.success(self.step_name, StructuralAnalysisResult(roots={}))) | ||
|
|
||
| roots: Dict[str, Optional[SgRoot]] = {} | ||
| for filename, sub_file in submission.submission_files.items(): | ||
| # Only parse files that likely contain code | ||
| if not self._is_code_file(filename): | ||
| continue | ||
|
|
||
| try: | ||
| roots[filename] = SgRoot(sub_file.content, ast_grep_lang) | ||
| except Exception as e: | ||
| logger.warning(f"Failed to parse {filename} with ast-grep: {e}") | ||
| roots[filename] = None | ||
|
|
||
| result = StructuralAnalysisResult(roots=roots) | ||
| return pipeline_exec.add_step_result(StepResult.success(self.step_name, result)) | ||
|
|
||
| def _map_language(self, language: Language) -> Optional[str]: | ||
| mapping = { | ||
| Language.PYTHON: "python", | ||
| Language.JAVA: "java", | ||
| Language.NODE: "javascript", | ||
| Language.CPP: "cpp", | ||
| Language.C: "c", | ||
| } | ||
| return mapping.get(language) | ||
|
|
||
| def _is_code_file(self, filename: str) -> bool: | ||
| """Heuristic to avoid parsing non-code files.""" | ||
| # Common binary/config/doc extensions to ignore | ||
| ignored_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.pdf', '.zip', '.tar', '.gz', '.json', '.yaml', '.yml', '.md', '.txt'} | ||
| return not any(filename.lower().endswith(ext) for ext in ignored_extensions) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the STRUCTURAL_ANALYSIS step ran but produced data=None (e.g., StepResult.fail), _require_step_data() will raise here. Consider checking the step result’s status/data and returning None when it’s not SUCCESS so later steps can proceed safely.