From f59f5ebf5eec5711c8f79e7e3596e523b54e0f22 Mon Sep 17 00:00:00 2001 From: Rafael Guterres Jeffman Date: Wed, 20 Mar 2024 16:16:04 -0300 Subject: [PATCH] =?UTF-8?q?Compiladores:=20Exerc=C3=ADcio=20T0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .exercicio_zero/configure | 23 ++++++ .github/workflows/grade.yml | 91 ++++++++++++++++++++++++ .gitignore | 14 ++++ COPYING | 20 ++++++ INSTRUCOES.md | 135 ++++++++++++++++++++++++++++++++++++ README.md | 71 +++++++++++++++++++ features/common_test.py | 33 +++++++++ features/environment.py | 46 ++++++++++++ features/listas.feature | 28 ++++++++ features/steps/listas.py | 90 ++++++++++++++++++++++++ pyproject.toml | 44 ++++++++++++ src/zero.py | 12 ++++ tox.ini | 14 ++++ 13 files changed, 621 insertions(+) create mode 100755 .exercicio_zero/configure create mode 100644 .github/workflows/grade.yml create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 INSTRUCOES.md create mode 100644 README.md create mode 100644 features/common_test.py create mode 100644 features/environment.py create mode 100644 features/listas.feature create mode 100644 features/steps/listas.py create mode 100644 pyproject.toml create mode 100644 src/zero.py create mode 100644 tox.ini diff --git a/.exercicio_zero/configure b/.exercicio_zero/configure new file mode 100755 index 0000000..41c92f6 --- /dev/null +++ b/.exercicio_zero/configure @@ -0,0 +1,23 @@ +#!/bin/sh + +TOPDIR="$(dirname $(dirname $(realpath $0)))" +EXZERODIR="${TOPDIR}/.exercicio_zero" + +. "${EXZERODIR}/project" + + +for file in "${TOPDIR}/pyproject.toml" "${TOPDIR}/README.md" +do +echo "Processing ${file}" +sed -i "${file}" -e " + s/@HOMEWORK@/${HOMEWORK}/g + s/@USER@/${USERNAME}/g + s/@REPO@/${REPOSITORY}/g + s/@WORKCODE@/${WORKCODE:-T${WORKNUM}}/g + s/@WORKNUM@/${WORKNUM}/g + s/@MONTH@/${MONTH:-$(date +"%m")}/g + s/@DISC@/${DISCIPLINA}/g + s/@CURSO@/${CURSO:-"Ciência da Computação"}/g + s/@UNIV@/${UNIVERSIDADE:-"Universidade LaSalle Canoas"}/g +" +done diff --git a/.github/workflows/grade.yml b/.github/workflows/grade.yml new file mode 100644 index 0000000..82c0c16 --- /dev/null +++ b/.github/workflows/grade.yml @@ -0,0 +1,91 @@ +# Copyright (c) 2024 Rafael Guterres Jeffman +# See the file COPYING for license details. +--- +name: grade_work +run-name: +on: [push, pull_request] +jobs: + lint_check: + name: Avaliação do pylint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - run: | + pip install pylint + pylint src + pep8_check: + name: Avaliação do flake8 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - run: | + pip install flake8 + flake8 --ignore=E501 src + docs_check: + name: Verifica documentação mínima do cốdigo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - run: | + pip install pydocstyle + pydocstyle src + run_tests: + name: Executa os testes de avaliação + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - run: | + pip install behave + pip install -e . + behave + - name: Arquiva resultados + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: homework_evaluation + path: RESULT + retention-days: 1 + report_result: + name: Resultados da avaliação + if: ${{ always() }} + needs: run_tests + runs-on: ubuntu-latest + steps: + - name: Download a single artifact + uses: actions/download-artifact@v3 + with: + name: homework_evaluation + - run: sed -n -e '$ ! p' RESULT + name: Resultado + report_grade: + name: Previsão de nota final + if: ${{ always() }} + needs: run_tests + runs-on: ubuntu-latest + steps: + - name: Download a single artifact + uses: actions/download-artifact@v3 + with: + name: homework_evaluation + - run: sed -n -e '$ p' RESULT + name: Previsão de Nota diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a61dbe --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# project configuration +.exercicio_zero/project + +# patch files +*.orig +*.rej + +# generated files +/**/__pycache__/ +RESULT + +# test tools +.coverage +.tox diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..022c1ca --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Copyright (c) 2024 Rafael Guterres Jeffman + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/INSTRUCOES.md b/INSTRUCOES.md new file mode 100644 index 0000000..eb24203 --- /dev/null +++ b/INSTRUCOES.md @@ -0,0 +1,135 @@ +Instruções para a realização do exercício +========================================= + +**Prazo máximo para a entrega do exercício: 27/03/2024** + +Objetivos +--------- + +O objetivo desse exercício é praticar o uso do Git e do Github, e entender +como será realizada a avaliação dos trabalhos de implementação da disciplina +de Compiladores. + +Objetivos secundários do exercício é familiarizar o aluno com ferramentas de +automação de testes da linguagem Python, como o `tox` e o `behave`. + + +Instruções +---------- + +O primeiro passo é realizar um _fork_ do projeto no +[Github](https://github.com/exercicios-programacao/compiladores-t0), para o +seu usuário. Esse passo irá criar um repositório do seu usuário que pode ser +utilizado para o desenvolvimento do trabalho. + +Após, você deverá obter uma cópia local do projeto, utilizando o `git`: + +``` +git clone https://github.com//compiladores-t0 +``` + +A partir dessa cópia local você irá implementar o código que falta para que +os testes existentes completem com sucesso. + +Para executar os testes, utilize o comando `behave`. (Veja como preparar o +ambiente de desenvolvimento no arquivo [README.md](README.md)). + +Uma vez que os testes executam corretamente, você pode verificar outras +métricas de qualidade de código, executando programas como `flake8`, +`pylint` ou `pydocstyle`. Estes programas fazem verificação de boas práticas +de codificação na linguagem Python. + +A forma mais simples de executar todos os comandos é utilizando o programa +`tox`, que foi configurado neste projeto para executar tanto os testes de +avaliação do código, como os testes de verificação. + +``` +tox +``` + +Para executar apenas os testes de boas práticas de codificação, execute: + +``` +tox -e lint +``` + +Uma vez que você está satisfeito com os resultados (mesmo que sejam ainda +resultados parciais), você pode _commitar_, localmente, seu trabalho: + +``` +git add +git commit +``` + +Para verificar a avaliação oficial, você precisará criar um +_pull request_. Para isso deve, primeiro, enviar seu trabalho para o seu +repositório no Github: + +``` +git pull +``` + +Nesse caso, se executado na linha de comando, o git irá mostrar um link +para criar o _pull request_ contra o repositório original. Basta completar +as informações e criar o _pull request_, que a avaliação oficial será +executada e pode ser acompanhada na aba _Actions_. + +Entre as informações que você precisa preencher estão: + +* Nome completo do aluno no _título_ do _pull request_. +* Citar quais foram suas dificuldades no campo de texto do _pull request_. + +Caso você não utilize a linha de comando o processo de criação do +_pull request_ deve ser realizado pelo Github, ou utilizando as ferramentas +do seu ambiente de desenvolvimento escolhido. + + +Tarefas +------- + +Você deve completar o código de forma a passar nos testes automatizados. +Para isso, você precisa implementar, no módulo `zero`, os métodos: + +* `procura_maior(dados: list) -> int` + : Retorna o inteiro com o maior valor, presente na lista. +* `procura_menor(dados: list) -> int` + : Retorna o inteiro com o menor valor, presente na lista. +* `procura_impares(dados: list) -> list` + : Retorna a lista de elementos ímpares, presentes na lista. +* `procura_pares(dados: list) -> list` + : Retorna a lista de elementos pares, presentes na lista. + +Como exemplo, parte do trabalho já foi realizado, e foi incluida a +implementação do método `procura_maior(dados: list) -> int`. + + +Importante +---------- + +* A nota final do trabalho será atribuída pelo professor, baseado na nota +da avaliação automática, de acordo com o resultado disponível na data de +entrega do trabalho. Um comentário irá marcar o número (_hash_) do _commit_ +utilizado para a avaliação. + +* Sempre que você alterar o estado do _branch_ que você utilizou para criar +o _pull request_, a avaliação será executada novamente, logo, você pode +utilizar essa característica para acompanhar o seu desenvolvimento. + +* Para este trabalho, apenas o arquivo `src/zero.py` pode ser alterado. + +* O resultado desse exercício não fará parte da nota do grau. + + +Sobre a Avaliação +----------------- + +Ao requisitar o _pull request_ uma série de testes serão executados e uma +indicação da nota obtida no trabalho será fornecida. Note que esta nota pode +aumentar ou diminuir dependendo de outros fatores, como falhas nos testes de +qualidade de código, que diminuiriam a nota, ou soluções criativas e/ou +elegantes, que aumentariam a nota. + +> **IMPORTANTE:** não é permitida a alteração de **nenhum** arquivo fora do +diretório `src`. Caso você queira adicionar mais testes, crie novos +arquivos no diretório `features`, mas não modifique os arquivos existentes. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6861137 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +compiladores-t0 +==================== + +Exemplo de projeto com avaliação automática de resultados. + +Preparação para a execução do trabalho +-------------------------------------- + +Para iniciar este trabalho, faça um _fork_ do repositório +[https://github.com/exercicios-programacao/compiladores-t0](https://github.com/exercicios-programacao/compiladores-t0) +para o seu usuário do Github. + +Siga as orientações para a preparação do ambiente de desenvolvimento +contidas nesse documento. + +Todo o código implementado deve estar dentro do diretório `src`. Siga as +instruções contidas no arquivo `INSTRUCOES.md`, que contém os objetivos e +etapas para a realização do trabalho. + + +Instalação das Dependências +--------------------------- + +Para realizar este trabalho você deverá utilizar a linguagem de programação +Python, na versão 3.11 ou superior. + +Para isolar o ambiente de desenvolvimento, é sugerido o uso de ambientes +virtuais do Pyhton, e você pode criar um ambiente virtual corretamente +configurado com os comandos: + +``` +$ python -m venv /tmp/compiladores-t0 +$ pip install -e . +``` + + +Desenvolvimento +--------------- + +Durante o desenvolvimento do trabalho, você pode executar os testes, +localmente, utilizando os comandos `tox` ou `behave`. A diferença entre os +dois é que o `behave` executa apenas os testes funcionais e o `tox` executa +os testes de qualidade de código, como formatação e boas práticas. + +É sugerido que se trabalhe em um cenário de cada vez, o que pode ser obtido +utilizando-se o comando `behave --stop`, para que os testes funcionais +parem na primeira falha. + + +Entrega +------- + +Para entregar o trabalho, faça commit do código, envie para o seu _fork_ no +Github, e abra um _pull request_ contra o +[repositório original](https://github.com/exercicios-programacao/compiladores-t0). + +O título do _pull request_ deve conter o nome do aluno que o está criando. +Na mensagem deve constar o nome completo do autor do _pull request_, e de +todos os alunos que realizaram o trabalho, no caso de trabalhos em grupo. +Qualquer informação necessária para a entrega do trabalho deve estar +presente no corpo dessa mensagem. + +Você deve garantir que os testes (`checks`) executaram corretamente, pois é +a partir deles que será realizada a avaliação. + + +Discussões Online +----------------- + +Dúvidas e disccussões sobre o trabalho podem ser realizadas utilizando as +[discussões do Github](https://github.com/exercicios-programacao/compiladores-t0/discussions). diff --git a/features/common_test.py b/features/common_test.py new file mode 100644 index 0000000..54df11f --- /dev/null +++ b/features/common_test.py @@ -0,0 +1,33 @@ +# SPDX-License-Identifer: MIT + +"""Commom test functionality.""" + +import sys + + +def import_module_error(module_name): + """Provide a better message when test module is not available.""" + print(f"Você implementou o módulo '{module_name}'?", file=sys.stderr) + sys.exit(1) + + +def import_function_error_message(module_name, function_name): + """Generate a better message for unimplemented tested function.""" + return ( + f"Você implementou a função '{function_name}' " + f"no módulo '{module_name}'?" + ) + + +def import_function_error(module_name, function_name): + """Provide a better message when failed to importe tested function.""" + print( + import_function_error_message(module_name, function_name), + file=sys.stderr, + ) + sys.exit(1) + + +def expected_observed_mismatch(expected, observed): + """Return a message when the expected/observed values mismatch.""" + return f"Value mismatch: expected={expected} - observed={observed}" diff --git a/features/environment.py b/features/environment.py new file mode 100644 index 0000000..b01a7e2 --- /dev/null +++ b/features/environment.py @@ -0,0 +1,46 @@ +# SPDX-License-Identifer: MIT + +"""Environment definition and grading implementation.""" + +import sys +import os +from behave.model import Status + + +# Code may be in "/src" directory +sys.path.append("src") + +__grade = {"success": [], "failed": []} + + +def after_all(_context): + """Save result to file RESULT.""" + positive = sum((value for _, value in __grade["success"]), 0) + negative = sum((value for _, value in __grade["failed"]), 0) + total = positive + negative + with open("RESULT", "wt") as result_file: # pylint: disable=W1514 + for result in ["success", "failed"]: + result_msg = "\n\t".join(name for name, _ in __grade[result]) + msg = f"{result.capitalize()} scenarios:\n\t{result_msg}" + # print(msg) + print(msg, file=result_file) + max_grade = os.environ.get("MAX_GRADE", total) + # print("MAX", max_grade) + # print("positive", positive) + # print("negative", negative) + grade_ratio = (max_grade * positive) / (1.0 * positive + negative) + final_grade = int(10 * grade_ratio) / 10 + # print(f"Grade: {final_grade} / {max_grade}") + print(f"{final_grade} / {max_grade}", file=result_file) + + +def after_scenario(_context, scenario): + """Add scenario to proper grading list.""" + weight_tags = [ + tag + for tag in scenario.tags + if tag.startswith("value") or tag.startswith("peso") + ] + ["default:1"] + peso = weight_tags[0].split(":")[-1] + result = "success" if scenario.status == Status.passed else "failed" + __grade[result].append((scenario.name, float(peso))) diff --git a/features/listas.feature b/features/listas.feature new file mode 100644 index 0000000..9b7430b --- /dev/null +++ b/features/listas.feature @@ -0,0 +1,28 @@ +# SPDX-License-Identifer: MIT + +# language: pt +Funcionalidade: Realiza operações sobre listas + +@peso:2 +Cenário: Procura o maior elemento em uma lista + Dada uma lista arbitrária + Quando procuro o maior elemento na lista + Então o resultado é o maior elemento da lista + + +Cenário: Procura o menor elemento em uma lista + Dada uma lista arbitrária + Quando procuro o menor elemento na lista + Então o resultado é o menor elemento da lista + + +Cenário: Procura todos os elementos ímpares em uma lista + Dada uma lista arbitrária + Quando procuro os elementos ímpares em uma lista + Então o resultado contém todos os elementos ímpares da lista e apenas os elementos ímpares + + +Cenário: Procura todos os elementos pares em uma lista + Dada uma lista arbitrária + Quando procuro os elementos pares em uma lista + Então o resultado contém todos os elementos pares da lista e apenas os elementos pares diff --git a/features/steps/listas.py b/features/steps/listas.py new file mode 100644 index 0000000..92d62fb --- /dev/null +++ b/features/steps/listas.py @@ -0,0 +1,90 @@ +# SPDX-License-Identifer: MIT + +"""Implementação dos testes para listas.""" + +from random import randint + +from behave import given, when, then # pylint: disable=no-name-in-module +from common_test import ( + import_module_error, + import_function_error_message, + expected_observed_mismatch, +) + +try: + import zero +except ImportError as imperr: + import_module_error(imperr.name) + + +def __assert_def(function_name): + assert hasattr(zero, function_name), import_function_error_message( + zero.__name__, function_name + ) + + +@given("uma lista arbitrária") +def _cria_lista(context): + context.lista = [randint(1, 1000) for _ in range(randint(1, 100))] + + +@when("procuro o maior elemento na lista") +def _procura_maior_elemento(context): + __assert_def("procura_maior") + context.result = zero.procura_maior(context.lista) + + +@then("o resultado é o maior elemento da lista") +def _compara_resultado_maior_elemento(context): + expected = max(context.lista) + assert context.result == expected, expected_observed_mismatch( + expected, context.result + ) + + +@when("procuro o menor elemento na lista") +def _procura_menor_elemento(context): + __assert_def("procura_menor") + context.result = zero.procura_menor(context.lista) + + +@then("o resultado é o menor elemento da lista") +def _compara_resultado_menor_elemento(context): + expected = min(context.lista) + assert context.result == expected, expected_observed_mismatch( + expected, context.result + ) + + +@when("procuro os elementos ímpares em uma lista") +def _procura_elementos_impares(context): + __assert_def("procura_impares") + context.result = zero.procura_impares(context.lista) + + +@then( + "o resultado contém todos os elementos ímpares da lista " + "e apenas os elementos ímpares" +) +def _compara_resultado_elementos_impares(context): + expected = [item for item in context.lista if item % 2 == 1] + assert context.result == expected, expected_observed_mismatch( + expected, context.result + ) + + +@when("procuro os elementos pares em uma lista") +def _procura_elementos_pares(context): + __assert_def("procura_pares") + context.result = zero.procura_pares(context.lista) + + +@then( + "o resultado contém todos os elementos pares da lista " + "e apenas os elementos pares" +) +def _compara_elementos_pares(context): + expected = [item for item in context.lista if item % 2 == 0] + assert context.result == expected, expected_observed_mismatch( + expected, context.result + ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4cc9cc4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,44 @@ +[project] +name = "compiladores-t0" +description = "Trabalho T0 da disciplina Compiladores, do curso de Ciência da Computação, da Universidade LaSalle Canoas." +readme = "README.md" +version = "0.03.2024" +requires-python = ">=3.11" +license = { file="COPYING" } + +classifiers = [ + "Intended Audience :: Education", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", +] + +dependencies = [ + "black", "pylint", "flake8", "pydocstyle", "behave", "pytest" +] + +[project.urls] +homepage = "https://github.com/rafasgj/compiladores-t0" +repository = "https://github.com/rafasgj/compiladores-t0" +issues = "https://github.com/rafasgj/compiladores-t0/issues" +pull_requests = "https://github.com/rafasgj/compiladores-t0/pulls" +discussions = "https://github.com/rafasgj/compiladores-t0/discussions" + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.black] +# line-length = 79 +target-version = ['py311', 'py312'] +include = '\.pyi?$' + +[tool.pytest.ini_options] +minversion = "6.0" +pythonpath = "." + +# pylint configuration +[tool.pylint.'BASIC'] +# max-line-length = 79 +max-attributes = 10 +good-names = [ + "i", "j", "k", "x", "y" +] diff --git a/src/zero.py b/src/zero.py new file mode 100644 index 0000000..97a299a --- /dev/null +++ b/src/zero.py @@ -0,0 +1,12 @@ +# SPDX-License-Identifer: MIT + +"""Implementação do exercício zero.""" + + +def procura_maior(lista): + """Procura maior item na lista usando procura linear.""" + maior = lista[0] + for item in lista[1:]: + if item > maior: + maior = item + return maior diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..b3ab24e --- /dev/null +++ b/tox.ini @@ -0,0 +1,14 @@ +[tox] +envlist = lint, clean, {py311, py312} + +[testenv:lint] +deps = .[lint] +commands = + flake8 src + pydocstyle src + pylint src + +[testenv] +deps = .[test] +commands = behave +