diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..e789935 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,94 @@ +# Basic workflow +name: build + +# Allow write permissions +permissions: + contents: write + +# Use more columns for terminal output +env: + COLUMNS: 120 + PYTHONIOENCODING: utf8 + +# Controls when the action will run +# Workflow begins with push or PR events +# Focuses on the main branch only +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +# Create a job matrix with different OSes +jobs: + build: + # do not allow a build to run for more than 5 minutes + timeout-minutes: 5 + # Use a matrix strategy to run on multiple OSes and Python versions + runs-on: ${{ matrix.os }} + strategy: + matrix: + # Test on multiple OSes and Python versions + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.12"] + # Define the workflow steps + steps: + # Checkout the code of the repository + - name: Check out Repository Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + # Setup Python for the current language version + - name: Setup Python ${{ matrix.python-version }} + if: always() + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + # Install pip + - name: Install Pip + if: always() + run: | + pip install -U pip + python -m pip install --user pipx + # Install poetry + - name: Install Poetry and Project Dependencies + if: always() + run: | + pipx install poetry + pipx list + cd exam + poetry install + # Confirm correctness with execexam and show + # detailed report information + - name: Confirm Correctness with ExecExam + if: always() + run: | + cd exam + poetry run execexam . ./tests/ --report trace --report status --report failure --report code --report setup --no-fancy + # Run GatorGrader: use the gatorgrade.yml in repository's root; + # note that this runs execexam for some of the checks + - name: Run GatorGrader with GatorGrade + if: always() + run: | + pipx install gatorgrade + pipx list + cd exam + gatorgrade --report env md GITHUB_STEP_SUMMARY + # Get the current time + - name: Get the Current Time + uses: josStorer/get-current-time@v2 + if: always() + id: current-time + with: + format: YYYYMMDD-HH-mm-ss + utcOffset: "-05:00" + # Write the collected GatorGrade data to the designated branch + - name: Write Collected Data to Designated Branch + uses: GatorEducator/BranchWrite@v1.0.1 + if: always() + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + branch: insight + path: insight/insight-report-${{steps.current-time.outputs.formattedTime}}.json + source: env + source-arg: JSON_REPORT \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..72f9ae9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,365 @@ +# Compiled source # +################### +*.class +*.o +*.so + +# Android # +########### +*.apk +*.ap_ +*.dex +local.properties + +# Packages # +############ +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# LaTeX # +######### +*.aux +*.dvi +*.fdb_latexmk +*.fls +*.lof +*.log +*.lot +*.out +*.pdf +*.ps +*.tex~ +*.toc +*_minted* + +# Bibliography aux files (bibtex/biblatex/biber) # +################################################## +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.run.xml + +## Build tool auxiliary files # +############################### +*.fdb_latexmk +*.synctex.gz +*.pdfsync +*.synctex.gz(busy) + +# Algorithms # +############## +*.alg +*.loa + +# Theorems # +############ +*.thm + +# Beamer # +########## +*.nav +*.snm +*.vrb + +# Glossaries # +############## +*.acn +*.acr +*.glg +*.glo +*.gls + +# Hyperref # +############ +*.brf + +# Listings # +############ +*.lol + +# Makeidx # +########### +*.idx +*.ilg +*.ind +*.ist + +# Minitoc # +########### +*.maf +*.mtc +*.mtc0 + +# Minted # +########## +*.pyg + +# Nomencl # +########### +*.nlo + +# Todonotes # +############# +*.tdo + +# Xindy # +######### +*.xdy + +# Vim # +####### +*.project.vim +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ + +# Ant # +####### +*.ant_targets + +# Java # +######## + +# Compiled class file # +####################### +*.class + +# Log file # +######## +*.log + +# BlueJ files # +############### +*.ctxt + +# Mobile Tools for Java (J2ME) # +################################ +.mtj.tmp/ + +# Package Files # +################# +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# JVM # +####### +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# Gradle # +########## +.gradle +gradle +/build +/buildSrc/build +/subprojects/*/build +/subprojects/docs/src/samples/*/*/build +/subprojects/internal-android-performance-testing/build-android-libs + +# Python # +########## +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +include/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +*.manifest +*.spec +pip-log.txt +pip-delete-this-directory.txt +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +*.mo +*.pot +*.log +local_settings.py +instance/ +.webassets-cache +.scrapy +docs/_build/ +target/ +.python-version +celerybeat-schedule +.env +.venv/ +venv/ +ENV/ +bin/ +share/ +lib64 +pip-selfcheck.json +pyvenv.cfg + +# R # +##### +*-Ex.R +*.RData +*.Rdata +*.Rhistory +*.Rincr_history + +# Ctags # +######### +tags +.tags +tags.lock +tags.temp +tags.tmp + +# Extra files # +############### +*.xmi +*.sync +*.jekyll-metadata + +# Editor Suggestions # +###################### + +# Vim # +####### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# VSCode # +########## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# Atom # +######## +.eslintcache +Thumbs.db +.project +.svn +.nvm-version +.vscode +node_modules +npm-debug.log +debug.log +/tags +/atom-shell/ +/out/ +docs/output +docs/includes +spec/fixtures/evil-files/ +out/ +/electron/ + +# Operating Systems # +##################### + +# macOS # +######### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Linux # +######### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# Poetry configuration for nixos +# to ensure that platform-dependent +# binaries are compiled locally +poetry.toml + +# Devenv files that are generated +.devenv +.devenv.flake.nix + +# Testing +coverage.json \ No newline at end of file diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..204a6bc --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,17 @@ +line-length = 88 # use the black default for line length +lint.ignore = [ + "E501", # do not check line length + "D203", # do not check blank line before class docstring + "D213", # do not check multi-line docstring summary on second line + "PLR1730", # do not require use of min or max functions +] +lint.select = [ + "E", # pycodestyle errors + "D", # pydocstyle errors + "I", # isort + "F", # Pyflakes + "PL", # pylint + "Q", # flake8-quotes + "RUF", # ruff-specific + "W", # pycodestyle warnings +] \ No newline at end of file diff --git a/README.md b/README.md index 2d02faf..845f311 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,104 @@ -# gatorgrade-examination-example-solution -An example solution repo for gatorgrade checks +# 🚀 Gatorgrade Examination Example: Solution + +![Platforms: Linux, MacOS, Windows](https://img.shields.io/badge/Platform-Linux%20%7C%20MacOS%20%7C%20Windows-blue.svg) +[![Language: Python](https://img.shields.io/badge/Language-Python-blue.svg)](https://www.python.org/) +[![Code Style: Black](https://img.shields.io/badge/Code%20Style-Black-blue.svg)](https://github.com/psf/black) +[![Commits: Conventional](https://img.shields.io/badge/Commits-Conventional-blue.svg)](https://www.conventionalcommits.org/en/v1.0.0/) + +## Table of Contents + + + +* [🚀 Executable Examination Example](#-executable-examination-example) + * [Table of Contents](#table-of-contents) + * [✨ Quick Task List](#-quick-task-list) + * [🧗 Introduction](#-introduction) + * [😁 Note: Student Name](#-note-student-name) + * [🚧 Honor Code](#-honor-code) + * [🌟 Re-type the sentence "I adhered to the Allegheny College Honor Code while completing this examination."](#-re-type-the-sentence-i-adhered-to-the-allegheny-college-honor-code-while-completing-this-examination) + * [🌐 Examination Overview](#-examination-overview) + * [📓 Distribution Requirements](#-distribution-requirements) + * [📙 Learning Objectives](#-learning-objectives) + + + +## ✨ Quick Task List + +- [X] Read the `Introduction` section for a brief overview +- [X] Read the `Honor Code` section and then digitally sign your pledge +- [X] Keep a running list of your sources in the `Honor Code` section +- [X] Read all of the content in this `README.md` file for more details +- [X] Complete the requested programming tasks in the files in the `exam/questions/` directory +- [X] Type `gatorgrade` in the `exam/` directory to assess the quality of your solution +- [X] Review the source code files in the `exam/tests/` to see all of the checks +- [X] Review the `exam/gatorgrade.yml` to see all check commands used to assess your work +- [X] Frequently use Git to `commit` and `push` your work to the repository +- [X] Adhere to all of the restrictions and regulations stated in this document + +## 🧗 Introduction + +If you are a student completing this assessment as part of a class at Allegheny +College, you will need to complete the programming tasks according to the +instructions inside of the Python source code files in the `exam/questions/` +directory of this repository. If you have questions about this assessment, +please see the course instructor during the assessment time period. You must +read and ensure that you understand all of the instructions in this file before +starting the assessment. + +## 😁 Note: Student Name + +Note: You must delete `Student Name` and add your name to the subsection header + +## 🚧 Honor Code + +- You **must** adhere to the Honor Code throughout your completion of the assessment +- You **must** answer all of the questions in the assessment using your own source code and documentation +- You **must** use your laptop computer and the development environment you setup on your laptop +- You **may** use any automated code and/or documentation generation tools to which you have access +- You **must** cite the source of any program code or documentation generated by any software tool +- You **must** cite any references that you consult to aid you in completing this assessment +- You **may not** discuss any aspect of the assessment with anyone except the course instructor +- You **may not** modify any part of the provided source code in the `exam/tests/` directory +- You **may not** modify any part of the provided source code in the `exam/gatorgrade.yml` file + +**IMPORTANT**: All students in this course are obligated to adhere to the +Allegheny College Honor Code throughout the completion of this assessment. If +the instructor detects that a student has committed a likely violation of the +Allegheny College Honor Code, this will result in the filing of a report with +the Dean of Students Office and the furnishing of all details about the likely +violation. Please make sure that you review the [Allegheny College Honor +Code](https://sites.allegheny.edu/about/honor-code/) before you start to take +this assessment. + +## 🌟 Re-type the sentence "I adhered to the Allegheny College Honor Code while completing this examination." + +Note: You must retype the sentence here in order to digitally sign your pledge. + +"I adhered to the Allegheny College Honor Code while completing this examination." + +**IMPORTANT:** If you do not type the required sentence then the course +instructor will not know that you adhered to the Allegheny College Honor Code +while completing the assessment. + +Note: Please list here the sources that you consulted while completing the examination! + +## 🌐 Example Examination Overview + +- **Examination Released**: At the start of your session +- **Examination Due**: At the end of your session + +**Please note that your `git push` access to the GitHub repository containing +the assessment will be disabled after the assessment's due date.** + +- The assessment is out of a total of 100 percent, with an automatically reported percentage. +- You must provide answers to all these questions by typing in the Python source code files. +- The final version of the Python source code files must be in your GitHub repository by the due date. +- You may run the automated assessment in your terminal window by using the `gatorgrade` command. +- Unless you already made special arrangements with the instructor, no late work will be accepted for this assessment. +- Unless you already made special arrangements with the course instructor, you must complete the examination +in Alden Hall. +- Unless you already made special arrangements with the course instructor, you must not take any examination +materials or your laptop computer out of Alden Hall when completing the examination. +- You may review details from running the automated assessment in GitHub by using the GitHub Actions tab. +- Your final score for this assessment is subject to revision following review by the course instructor. +- You may talk to the course instructor if you have questions about or you need troubleshooting help with this assessment. diff --git a/exam/gatorgrade.yml b/exam/gatorgrade.yml new file mode 100644 index 0000000..37c2bb9 --- /dev/null +++ b/exam/gatorgrade.yml @@ -0,0 +1,151 @@ +setup: | + echo "🐊 Attempt to install software engineering tools" + echo "🐊 Will not re-install if already installed" + poetry install +--- + +# Honor Code {{{ + +- ../README.md: + - description: Ensure that the README.md file exists inside of the root of the GitHub repository + check: ConfirmFileExists + - description: Delete the phrase 'Add Your Name Here' and add your own name as an Honor Code pledge in README.md + check: MatchFileFragment + options: + fragment: "Add Your Name Here" + count: 0 + exact: true + - description: Retype the every word in the Honor Code pledge in README.md + check: MatchFileFragment + options: + fragment: "I adhered to the Allegheny College Honor Code while completing this examination." + count: 3 + exact: true + - description: Indicate that you have completed all of the tasks in the README.md + check: MatchFileFragment + options: + fragment: "- [X]" + count: 10 + exact: true + +# }}} + +# Basic Program Characteristics {{{ + +# Question 1 + +# perform checks on the program file +- questions/question_one.py: + # --> file exists in the correct directory + - description: Ensure that question_one.py file exists in the questions/ directory + check: ConfirmFileExists + # --> no remaining TODO markers in the source code + - description: Complete all TODOs, remove the TODO markers, and rewrite comments for question_one.py + check: MatchFileFragment + options: + fragment: TODO + count: 0 + exact: true + # --> contains suitable number of docstrings for both module and the function (note, differs from symbex) + - description: Create a sufficient number of docstring (i.e., multiple-line) comments in question_one.py + check: CountMultipleLineComments + options: + language: "Python" + count: 4 + exact: true + +# perform checks on the test file +- tests/test_question_one.py: + # --> file exists in the correct directory + - description: Ensure that test_question_one.py file exists in the tests/ directory + check: ConfirmFileExists + +# }}} + +# Program Output {{{ + +# Question 1 + +- description: Run checks for Question 1 Part (a) with 'execexam' command and confirm correct exit code + command: poetry run execexam . tests/ --mark "question_one_part_a" --no-fancy + objectives: + LO1: + degree: + - CS: "I" + rationale: "Manipulate the matrix data structure" + LO5: + degree: + - CS: "D" + rationale: "Search a matrix and compute summary values based on matrix" + +- description: Run checks for Question 1 Part (b) with 'execexam' command and confirm correct exit code + command: poetry run execexam . tests/ --mark "question_one_part_b" --no-fancy + objectives: + LO1: + degree: + - CS: "I" + rationale: "Manipulate the matrix data structure" + LO5: + degree: + - CS: "D" + rationale: "Search a matrix and compute summary values based on matrix" + +- description: Run checks for Question 1 Part (c) with 'execexam' command and confirm correct exit code + command: poetry run execexam . tests/ --mark "question_one_part_c" --no-fancy + objectives: + LO1: + degree: + - CS: "I" + rationale: "Manipulate the matrix data structure" + LO5: + degree: + - CS: "D" + rationale: "Search a matrix and compute summary values based on matrix" + +# }}} + +# Program Analysis with Software Engineering Tools {{{ + +# Question 1 + +# run a command: ruff check with default settings found in .ruff.toml file +- description: Ensure that Question 1 follows industry-standard rules using the command 'ruff check' + command: poetry run ruff check questions/question_one.py + +# run a command: ruff format with default settings found in .ruff.toml file +- description: Ensure that Question 1 adheres to an industry-standard format using the command 'ruff format' + command: poetry run ruff format questions/question_one.py --check + +# run a command: mypy with the default settings to confirm type alignment for all functions +- description: Ensure that Question 1 has correct type annotations using the command 'mypy' + command: poetry run mypy questions/question_one.py + +# run symbex: count functions with full type annotations +- description: Ensure that Question 1 has correct number of fully type annotated functions using the command 'symbex' + check: MatchCommandFragment + options: + command: "poetry run symbex -s --fully-typed -f questions/question_one.py --count" + fragment: 3 + count: 1 + exact: true + +# run symbex: count functions with docstring-based documentation (note: does not count module docstring) +- description: Ensure that Question 1 has correct number of documented functions using the command 'symbex' + check: MatchCommandFragment + options: + command: "poetry run symbex -s --documented -f questions/question_one.py --count" + fragment: 3 + count: 1 + exact: true + +# run symbex: confirm that there are no functions that are undocumented (i.e., do not have a docstring) +- description: Ensure that Question 1 has no undocumented functions using the command 'symbex' + check: MatchCommandFragment + options: + command: "poetry run symbex -s --undocumented -f questions/question_one.py --count" + fragment: 0 + count: 1 + exact: true + + +# }}} \ No newline at end of file diff --git a/exam/pyproject.toml b/exam/pyproject.toml new file mode 100644 index 0000000..cc818db --- /dev/null +++ b/exam/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "questions" +version = "0.1.0" +description = "" +authors = ["Pallas-Athena Cain "] + +[tool.poetry.dependencies] +python = "^3.11" +symbex = "^1.4" +pytest-json-report = "^1.5.0" +execexam = "^0.3.0" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.1.1" +mypy = "^1.9.0" +ruff = {version = "^0.6.3"} + +[tool.pytest.ini_options] +filterwarnings = [ + "error", +] +enable_assertion_pass_hook = true + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/exam/questions/__init__.py b/exam/questions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/exam/questions/question_one.py b/exam/questions/question_one.py new file mode 100644 index 0000000..54d34d1 --- /dev/null +++ b/exam/questions/question_one.py @@ -0,0 +1,140 @@ +"""Question One: Example Executable Examination.""" + +# Note: The imports in the following source code block may no longer +# adhere to the industry best practices for Python source code. +# You must reorganize and/or add the imports so that they adhere +# to the industry best practices for Python source code. + +from typing import List, Union + +# Introduction: Read This First! {{{ + +# Keep in mind these considerations as you implement the required functions: + +# --> You must implement Python functions to complete each of these steps, +# bearing in mind that one defective function may break another function. + +# --> Your source code must adhere to industry best practices in, for instance, +# source code formatting, variable naming, and documentation. + +# --> You may refer to the checks that are specified in the exam/gatorgrade.yml file +# in this GitHub repository for the configuration and name of each tool used +# to analyze the code inside of this file. + +# }}} + +# Part (a) {{{ + +# Instructions: Implement the following function so that it adheres to all +# aspects of the following specification. + +# Function specification: +# The function find_minimum_value should: +# - Take a list of lists of integers, called matrix, as its parameter +# - Return an integer that represents the minimum value in the matrix + +# Note: If the function is called with an invalid input (e.g., an empty +# matrix), it should return None. + +# Note: This function may not not have all of the correct type annotations for +# certain variables. You must add all of any needed type annotations +# so that the function and any code that uses it passes the type checker. + +# Note: This function may not have a docstring and thus it may not adhere +# to industry best practices for Python source code. You may need to add a docstring +# so that this function is correctly documented by an algorithm engineer using it. + + +def find_minimum_value(matrix: List[List[int]]) -> Union[int, None]: + """Return the minimum value in the provided matrix.""" + # confirm that there is a value in the [0][0] position + if not matrix or not matrix[0]: + return None + minimum_value = matrix[0][0] + for row in matrix: + for value in row: + if value < minimum_value: + minimum_value = value + return minimum_value + + +# }}} + +# Part (b) {{{ + +# Instructions: Implement the following function so that it adheres to all +# aspects of the following specification. + +# Function specification: +# The function find_maximum_value should: +# - Take a list of lists of integers, called matrix, as its parameter +# - Return an integer that represents the maximum value in the matrix + +# Note: If the function is called with an invalid input (e.g., an empty +# matrix), it should return None. + +# Note: This function may not not have all of the correct type annotations for +# certain variables. You must add all of any needed type annotations +# so that the function and any code that uses it passes the type checker. + +# Note: This function may not have a docstring and thus it may not adhere +# to industry best practices for Python source code. You may need to add a docstring +# so that this function is correctly documented by an algorithm engineer using it. + + +def find_maximum_value(matrix: List[List[int]]) -> Union[int, None]: + """Return the maximum value in the provided matrix.""" + # confirm that there is a value in the [0][0] position + if not matrix or not matrix[0]: + return None + maximum_value = matrix[0][0] + for row in matrix: + for value in row: + if value > maximum_value: + maximum_value = value + return maximum_value + + +# }}} + + +# Part (c) {{{ + +# Instructions: Implement the following function so that it adheres to all +# aspects of the following specification. + +# Function specification: +# The function find_average_value should: +# - Take a list of lists of integers, called matrix, as its parameter +# - Return a floating-point number that represents the average value of all numbers in the matrix + +# Note: If the function is called with an invalid input (e.g., an empty +# matrix), it should return None. + +# Note: This function may not not have all of the correct type annotations for +# certain variables. You must add all of any needed type annotations +# so that the function and any code that uses it passes the type checker. + +# Note: This function may not have a docstring and thus it may not adhere +# to industry best practices for Python source code. You may need to add a docstring +# so that this function is correctly documented by an algorithm engineer using it. + + +def find_average_value(matrix: List[List[int]]) -> Union[float, None]: + """Find the average value in the provided matrix.""" + # check if the matrix is empty + if not matrix or not matrix[0]: + return None + # initialize sum and count variables + total_sum = 0 + count = 0 + # iterate over the matrix to calculate the sum and count the number of elements + for row in matrix: + for value in row: + total_sum += value + count += 1 + # calculate and return the average value + return total_sum / count + + +# }}} \ No newline at end of file diff --git a/exam/shell.nix b/exam/shell.nix new file mode 100644 index 0000000..62afc46 --- /dev/null +++ b/exam/shell.nix @@ -0,0 +1,9 @@ +with import {}; + +mkShell { + buildInputs = [ stdenv.cc.cc.lib ]; + + shellHook = '' + export LD_LIBRARY_PATH=${stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH + ''; +} \ No newline at end of file diff --git a/exam/tests/__init__.py b/exam/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/exam/tests/conftest.py b/exam/tests/conftest.py new file mode 100644 index 0000000..49fd06f --- /dev/null +++ b/exam/tests/conftest.py @@ -0,0 +1,15 @@ +"""Pytest configuration for the exam package.""" + +import sys + +import pytest + +traced_functions_map = {} + + +def pytest_configure(config): + """Add markers to the pytest configuration.""" + # Question 1 + config.addinivalue_line("markers", "question_one_part_a: Question 1 Part (a)") + config.addinivalue_line("markers", "question_one_part_b: Question 1 Part (b)") + config.addinivalue_line("markers", "question_one_part_c: Question 1 Part (c)") \ No newline at end of file diff --git a/exam/tests/test_question_one.py b/exam/tests/test_question_one.py new file mode 100644 index 0000000..b112308 --- /dev/null +++ b/exam/tests/test_question_one.py @@ -0,0 +1,80 @@ +"""Confirm the correctness of functions in question_one.""" + +import pytest + +# ruff: noqa: PLR2004 +from questions.question_one import ( + find_average_value, + find_maximum_value, + find_minimum_value, +) + + +@pytest.mark.question_one_part_a +def test_find_minimum_value(): + """Confirm correctness of question part.""" + # check 1: Matrix with positive values + matrix = [[5, 7, 3], [1, 9, 2], [6, 4, 8]] + minimum_positive = find_minimum_value(matrix) + assert ( + 1 == minimum_positive and minimum_positive is not None + ), "Minimum positive value in matrix" + # check 2: Matrix with negative values + matrix = [[-2, 5, 1], [-3, 0, 6], [-1, -4, 7]] + minimum_negative = find_minimum_value(matrix) + assert ( + -4 == minimum_negative and minimum_negative is not None + # ) + ), "Minimum negative value in matrix" + # check 3: Matrix with a single element + matrix = [[10]] + minimum_single = find_minimum_value(matrix) + assert ( + 10 == minimum_single and minimum_single is not None + ), "Minimum value in single matrix" + # check 4: Empty matrix + matrix = [] + minimum_empty = find_minimum_value(matrix) + assert minimum_empty is None, "Minimum value in empty matrix" + + +@pytest.mark.question_one_part_b +def test_find_maximum_value(): + """Confirm correctness of question part.""" + # check 1: Matrix with positive values + matrix = [[5, 7, 3], [1, 9, 2], [6, 4, 8]] + maximum_positive = find_maximum_value(matrix) + assert 9 == maximum_positive, "Maximum positive value in matrix" + # check 2: Matrix with negative values + matrix = [[-2, 5, 1], [-3, 0, 6], [-1, -4, 7]] + maximum_negative = find_maximum_value(matrix) + assert 7 == maximum_negative, "Maximum negative value in matrix" + # check 3: Matrix with a single element + matrix = [[10]] + maximum_single = find_maximum_value(matrix) + assert 10 == maximum_single, "Maximum value in single matrix" + # check 4: Empty matrix + matrix = [] + maximum_empty = find_maximum_value(matrix) + assert maximum_empty is None, "Maximum value in empty matrix" + + +@pytest.mark.question_one_part_c +def test_find_average_value(): + """Confirm correctness of a question part.""" + # check 1: Matrix with positive values + matrix = [[5, 7, 3], [1, 9, 2], [6, 4, 8]] + average_positive = find_average_value(matrix) + assert 5.0 == average_positive, "Average value in matrix with positive numbers" + # check 2: Matrix with negative values + matrix = [[-2, 5, 1], [-3, 0, 6], [-1, -4, 7]] + average_negative = find_average_value(matrix) + assert 1.0 == average_negative, "Average value in matrix with negative numbers" + # check 3: Matrix with a single element + matrix = [[10]] + average_single = find_average_value(matrix) + assert 10.0 == average_single, "Average value in single element matrix" + # check 4: Empty matrix + matrix = [] + average_empty = find_average_value(matrix) + assert average_empty is None, "Average value in empty matrix" \ No newline at end of file