Skip to content

[Feature] Extract static analysis tests and add multi-template support (#305)#306

Merged
ArthurCRodrigues merged 3 commits intomainfrom
feat/issue-305-multi-template-support
May 7, 2026
Merged

[Feature] Extract static analysis tests and add multi-template support (#305)#306
ArthurCRodrigues merged 3 commits intomainfrom
feat/issue-305-multi-template-support

Conversation

@ArthurCRodrigues
Copy link
Copy Markdown
Member

Summary

  • migrated forbidden_import and forbidden_keyword tests to a new static_analysis template
  • updated InputOutputTemplate to remove static analysis tests
  • updated build_pipeline and TemplateLoaderStep to accept a list of template names or a comma-separated string
  • updated CriteriaTreeService to lookup test implementations across all loaded templates
  • updated SandboxStep and GradeStep to initialize the sandbox only if at least one loaded template requires it
  • safely skip StructuralAnalysisStep if ast-grep-py is missing instead of failing the pipeline

Validation

  • pytest tests/unit/ ✅ (unit tests passed)
  • manually verified pipeline construction correctly loads multiple templates and combines test capabilities

Closes #305

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the autograder to support loading multiple grading templates per assignment, and extracts static-analysis-only checks into a dedicated static_analysis template so they can be combined with execution-based templates like input_output.

Changes:

  • Added a new static_analysis template and migrated forbidden_import / forbidden_keyword out of input_output.
  • Updated pipeline steps and services (TemplateLoaderStep, PipelineExecution, CriteriaTreeService, SandboxStep, GradeStep, BuildTreeStep) to work with multiple loaded templates.
  • Updated i18n translation files and unit tests to reflect the new template structure.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/unit/test_test_function_validation.py Updates CriteriaTreeService usage to pass templates as a list.
tests/unit/test_forbidden_keyword.py Switches registration test to StaticAnalysisTemplate.
tests/unit/test_forbidden_import.py Switches registration test to StaticAnalysisTemplate.
tests/unit/test_command_resolution_at_build_time.py Updates imports to pull ForbiddenImportTest from static_analysis.
autograder/translations/pt_br.json Moves forbidden_import strings under static_analysis and updates io template block.
autograder/translations/en.json Moves forbidden_import strings under static_analysis and updates io template block.
autograder/template_library/static_analysis.py New template implementing forbidden_import + forbidden_keyword.
autograder/template_library/input_output.py Removes static analysis tests from InputOutputTemplate.
autograder/steps/structural_analysis_step.py Changes missing ast-grep-py behavior from pipeline-fail to “skip”.
autograder/steps/sandbox_step.py Skips sandbox creation unless any loaded template requires it.
autograder/steps/load_template_step.py Loads multiple templates from list or comma-separated string.
autograder/steps/grade_step.py Requires sandbox only if any loaded template needs it.
autograder/steps/build_tree_step.py Builds criteria tree from multiple templates’ test registries.
autograder/services/template_library_service.py Registers static_analysis as a built-in template.
autograder/services/criteria_tree_service.py Looks up test implementations across all loaded templates.
autograder/models/pipeline_execution.py Adds get_loaded_templates() and keeps get_loaded_template() for compatibility.
autograder/autograder.py Updates build_pipeline docstring to reflect list/string templates.

Comment thread autograder/services/criteria_tree_service.py Outdated
Comment thread autograder/steps/load_template_step.py Outdated
@@ -1,4 +1,5 @@
import logging
from typing import Union, List, Optional
Comment thread autograder/steps/load_template_step.py Outdated
Comment on lines +22 to +24
self._template_names = [name.strip() for name in template_name.split(",")]
else:
self._template_names = template_name
Comment on lines +86 to +87
for tmpl in templates:
raw = tmpl.replace('{{lib}}', library).replace('{lib}', re.escape(library))
Comment on lines +31 to +37
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"))
logger.warning("ast-grep-py is not installed; skipping structural analysis.")
return pipeline_exec.add_step_result(StepResult.success(self.step_name, StructuralAnalysisResult(roots={})))
Comment on lines +237 to +285
if not forbidden_keywords and not custom_ast_grep_rules:
return TestResult(
test_name=self.name,
score=100.0,
report=t("static_analysis.forbidden_keyword.report.no_rules", locale=locale)
)

if structural_analysis is None:
return TestResult(
test_name=self.name,
score=0.0,
report=t("static_analysis.forbidden_keyword.report.no_analysis", locale=locale)
)

if submission_language is None:
return TestResult(
test_name=self.name,
score=0.0,
report=t("static_analysis.forbidden_keyword.report.no_lang", locale=locale)
)

active_rules: List[Dict[str, Any]] = list(custom_ast_grep_rules)
lang_predefined = self.PREDEFINED_RULES.get(submission_language, {})

for kw in forbidden_keywords:
if kw in lang_predefined:
active_rules.append(lang_predefined[kw])

if not active_rules:
return TestResult(
test_name=self.name,
score=100.0,
report=t("static_analysis.forbidden_keyword.report.success", locale=locale)
)

all_violations: List[str] = []

if not files:
return TestResult(
test_name=self.name,
score=100.0,
report=t("static_analysis.forbidden_keyword.report.no_files", locale=locale)
)

for sub_file in files:
root = structural_analysis.roots.get(sub_file.filename)
if root is None:
continue

ArthurCRodrigues and others added 2 commits May 6, 2026 16:01
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- prevent duplicate submission_language kwargs in grader with runtime precedence
- normalize and validate multi-template names in TemplateLoaderStep
- remove stale CriteriaTreeService template state
- add explicit structural-analysis availability semantics
- harden ForbiddenKeywordTest when analysis roots are unavailable
- fix ForbiddenImportTest regex escaping for placeholder variants
- add regression tests for collision path, template normalization, regex escaping, and analysis-unavailable cases

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ArthurCRodrigues
Copy link
Copy Markdown
Member Author

Implemented the remaining fixes for #305 and pushed commit 0fd3cd2 to this PR branch.

Fixed

  • Prevented submission_language kwarg collision in SubmissionGrader.process_test() by passing it exactly once and applying runtime-language precedence over config.
  • Removed stale CriteriaTreeService state (__template) and standardized __templates initialization.
  • Hardened TemplateLoaderStep template-name normalization for both string/list inputs:
    • trim whitespace,
    • drop empty entries,
    • fail early when the final list is empty,
    • keep comma-separated compatibility.
  • Fixed regex escaping in ForbiddenImportTest for both placeholder variants ({lib} and {{lib}}) so special chars are treated literally.
  • Added explicit structural-analysis availability semantics:
    • StructuralAnalysisResult.available + reason,
    • StructuralAnalysisStep marks unavailable/unsupported/missing-language cases explicitly.
  • Updated ForbiddenKeywordTest to fail as no-analysis when structural analysis is unavailable or required roots are missing.

Added regression coverage

  • submission_language collision path, including multi-template grading path.
  • Template normalization edge cases.
  • Forbidden import with regex-special module names (foo.bar, a+b).
  • Structural-analysis unavailable/missing-root behavior.
  • Template metadata consistency check for stable identifier field.

Validation run

  • Targeted + new regression suites: 128 passed.
  • Manual API smoke via endpoints:
    • single-template (static_analysis) config/submission graded successfully,
    • multi-template (webdev,static_analysis) config/submission graded successfully,
    • both produced deterministic forbidden-import failure score and no GradeStep interruption.
  • Full pytest still reports pre-existing environment/integration failures unrelated to this change set (same baseline issue category: integration infra/service-dependent tests).

Copy link
Copy Markdown
Member Author

@ArthurCRodrigues ArthurCRodrigues left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@ArthurCRodrigues ArthurCRodrigues merged commit 91035f0 into main May 7, 2026
2 checks passed
@ArthurCRodrigues ArthurCRodrigues deleted the feat/issue-305-multi-template-support branch May 7, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Extract static analysis tests and add multi-template support

2 participants