Add Numbers

Assignment

Write a program that has a function called add_num that takes two parameters \(x\) and \(y\), and returns their sum \(x+y\).

Example Solution

def add_num(x, y):
    return x + y

Example Grader

from __future__ import annotations

import importlib.util
import sys
import traceback
from collections.abc import Callable
from pathlib import Path

student_code_path: str = sys.argv[2]
username: str = sys.argv[3]
log_file = Path(sys.argv[4])

test_cases = (
    (1, 2),
    (3, 4),
    (1000, 20345),
    (54, 78),
)

secret_test_cases = (
    (127, 856.7),
    (789.101, 101112),
)


def import_module(modname: str = "student_submission", func_name="add_num") -> Callable:
    """Imports the student submission and returns the function with the given name.

    It accomplishes this by utilizing a lot of the machinery provided by the python module
    ``importlib``. If you don't understand how it works, feel free to just copy paste this
    function and pass a different value for the ``func_name`` parameter.
    """

    spec = importlib.util.spec_from_file_location(modname, student_code_path)

    # these are probably grader errors and not student errors, so we raise an
    # exception instead of printing
    if spec is None:
        raise ImportError(f"Could not load spec for module {student_code_path!r}")
    if spec.loader is None:
        raise ImportError(f"No loader found for module {student_code_path!r} with {spec=!r}")

    submission = importlib.util.module_from_spec(spec)

    if submission is None:
        raise ImportError("Module spec is None")

    sys.modules[modname] = submission

    try:
        spec.loader.exec_module(submission)
    except Exception:
        # this traceback could provide sensitive information, so we don't provide it to students
        print("Could not test submission, an exception was raised while initializing.")
        log_file.write_text(f"Student {username} import error:\n\n" + traceback.format_exc())
        # it's not our fault so we exit 0
        sys.exit(0)

    try:
        func = getattr(submission, func_name)
    except AttributeError:
        print(f"Could not find function {func_name!r}")
        sys.exit(0)

    return func


def run_submission(func: Callable) -> None:
    # grade submissions
    failing_cases = 0
    tol = 1e-8
    for x, y in test_cases:
        try:
            # take into account floating point error
            if func(x, y) - (x + y) > tol:
                print(f"Failed on test case {x=},{y=}")
                failing_cases += 1
        except Exception:
            print(f"Code errored on test case {x=},{y=}")
            failing_cases += 1

    for idx, (x, y) in enumerate(secret_test_cases):
        try:
            if func(x, y) - (x + y) > tol:
                print(f"Failed on secret test case {idx}")
                failing_cases += 1
        except Exception:
            print(f"Code errored on secret test case {idx}")
            failing_cases += 1

    raw = 1 - failing_cases / (len(test_cases) + len(secret_test_cases))
    # print score, rounding to two decimal places
    print(f"Score: {raw * 100:.2f}%")


def main() -> None:
    submission = import_module()
    run_submission(submission)


if __name__ == "__main__":
    main()