diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index f12b71a..60cd0f1 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -1,32 +1,44 @@ -name: Python package +# mash-up of matrix and pipenv examples from +name: Test + +on: + push: + branches: [master] + pull_request: + types: [opened, synchronize, reopened] # default events -on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest strategy: - max-parallel: 4 + fail-fast: false # prevent one broken version from stopping all other runs matrix: - python-version: [3.5, 3.6, 3.7, 3.8] - + # these will be out of date again eventually, add the latest versions + # and remove the unsupported ones + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] + name: Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + - uses: actions/checkout@v3 + - name: Install pipenv + run: pipx install pipenv + - name: Set up Python + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} + architecture: x64 + cache: 'pipenv' - name: Install dependencies - run: | - pip install pipenv - pipenv install --dev + run: pipenv install --dev + - name: Lint with flake8 + run: pipenv run flake8 *.py + continue-on-error: true + - name: Typecheck with mypy + run: pipenv run mypy *.py - name: Test with unittest - run: | - pipenv run python -m unittest discover -v - - name: Install with pip - run: | - pip install . + run: pipenv run python -m unittest discover -v + - name: Install as package with pip + run: pip install . - name: Run installed version run: | focstest --version diff --git a/Pipfile b/Pipfile index 6a59c3f..57b1672 100644 --- a/Pipfile +++ b/Pipfile @@ -13,3 +13,8 @@ termcolor = "*" [dev-packages] "flake8" = "*" +mypy = "*" +types-setuptools = "*" +types-beautifulsoup4 = "*" +types-requests = "*" +types-termcolor = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 4aabdcf..ad80b70 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "ca175c24de58e3ab26e0d8373f7175b90694b1714805f9741be9ad8476a1988e" + "sha256": "44a7866c0d65bbe19b3d874113e2b4db2ffbf68b98c8b78eb345fb921c371347" }, "pipfile-spec": 6, "requires": { - "python_version": "3.5" + "python_version": "3" }, "sources": [ { @@ -18,11 +18,11 @@ "default": { "beautifulsoup4": { "hashes": [ - "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35", - "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25", - "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666" + "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", + "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" ], - "version": "==4.9.3" + "markers": "python_version >= '3.6'", + "version": "==4.11.1" }, "bs4": { "hashes": [ @@ -33,40 +33,42 @@ }, "certifi": { "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" ], - "version": "==2020.12.5" + "version": "==2021.10.8" }, - "chardet": { + "charset-normalizer": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" ], - "version": "==3.0.4" + "markers": "python_version >= '3.0'", + "version": "==2.0.12" }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" ], - "version": "==2.8" + "markers": "python_version >= '3.0'", + "version": "==3.3" }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" ], "index": "pypi", - "version": "==2.22.0" + "version": "==2.27.1" }, "soupsieve": { "hashes": [ - "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc", - "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b" + "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", + "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" ], - "markers": "python_version >= '3.0'", - "version": "==2.2.1" + "markers": "python_version >= '3.6'", + "version": "==2.3.2.post1" }, "termcolor": { "hashes": [ @@ -77,28 +79,21 @@ }, "urllib3": { "hashes": [ - "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", - "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" ], - "index": "pypi", - "version": "==1.25.8" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.9" } }, "develop": { - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "version": "==0.3" - }, "flake8": { "hashes": [ - "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", - "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696" + "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", + "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d" ], "index": "pypi", - "version": "==3.7.8" + "version": "==4.0.1" }, "mccabe": { "hashes": [ @@ -107,19 +102,112 @@ ], "version": "==0.6.1" }, + "mypy": { + "hashes": [ + "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e", + "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16", + "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2", + "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c", + "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67", + "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5", + "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b", + "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6", + "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58", + "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d", + "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17", + "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0", + "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca", + "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6", + "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322", + "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534", + "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3", + "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db", + "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904", + "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c", + "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46", + "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8", + "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee" + ], + "index": "pypi", + "version": "==0.942" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, "pycodestyle": { "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", + "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" ], - "version": "==2.5.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.8.0" }, "pyflakes": { "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", + "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.0" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.1" + }, + "types-beautifulsoup4": { + "hashes": [ + "sha256:74a35a3e09729e0fb67006f5526864ad6baa610de2d425f0df89e5e79ee76b3a", + "sha256:77348fb90156aad33abe6ac29fbcbd097c83756a4adf05c942ce20387344a9b2" + ], + "index": "pypi", + "version": "==4.10.18" + }, + "types-requests": { + "hashes": [ + "sha256:2437a5f4d16c0c8bd7539a8126d492b7aeb41e6cda670d76b286c7f83a658d42", + "sha256:c8010c18b291a7efb60b1452dbe12530bc25693dd657e70c62803fcdc4bffe9b" + ], + "index": "pypi", + "version": "==2.27.16" + }, + "types-setuptools": { + "hashes": [ + "sha256:415a1c23101a05da17eb66bed5d5a865702e5a69f74c66dbf1af643dce9492ab", + "sha256:c3aa952535dedc78654459dfdee49c66974a6c67bdd8026e7e69ae39f80d590b" + ], + "index": "pypi", + "version": "==57.4.12" + }, + "types-termcolor": { + "hashes": [ + "sha256:b97d20d5c649431bac4ab75f9a9718113f11cbdbc0dd1faac61ae54ac93df77b", + "sha256:be8626040770f788ee5a99aa3246ee5996f015bebba076d258c59c09cc40be86" + ], + "index": "pypi", + "version": "==1.1.3" + }, + "types-urllib3": { + "hashes": [ + "sha256:24d64e441168851eb05f1d022de18ae31558f5649c8f1117e384c2e85e31315b", + "sha256:bd0abc01e9fb963e4fddd561a56d21cc371b988d1245662195c90379077139cd" + ], + "version": "==1.26.11" + }, + "typing-extensions": { + "hashes": [ + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" ], - "version": "==2.1.1" + "markers": "python_version >= '3.6'", + "version": "==4.1.1" } } } diff --git a/focstest.py b/focstest.py index c9f93ee..4fcf808 100755 --- a/focstest.py +++ b/focstest.py @@ -29,16 +29,16 @@ # default url matching -BASE_URL = "http://rpucella.net/courses/focs-fa20/homeworks/" # website and path to look under -OCAML_FILE_PATTERN = "homework(\d{1,2}).ml" # pattern to pass the user-given ocaml file +BASE_URL = "http://rpucella.net/courses/focs-fa20/homeworks/" # default website and path to fetch from +OCAML_FILE_PATTERN = r"homework(\d{1,2}).ml" # pattern to extract homework number from the user-given ocaml file HTML_FILE_TEMPLATE = "homework{}.html" # template to build the html filename given a homework number # selectors for parsing html CODE_BLOCK_SELECTOR = 'pre code' # css selector to get code blocks # regex patterns for parsing text -TEST_PATTERN = "^(.+;;)\n(.*)$" # pattern to get input and output -OCAML_PATTERN = "^(.*)" # pattern to grab output of lines +TEST_PATTERN = r"^(.+;;)\n(.*)$" # pattern to get input and output +OCAML_PATTERN = r"^(.*)" # pattern to grab output of lines # compile regexes ahead of time OCAML_FILE_COMP = re.compile(OCAML_FILE_PATTERN) @@ -55,8 +55,7 @@ def get_blocks(html): page = BeautifulSoup(html, 'html.parser') code_blocks = page.select(CODE_BLOCK_SELECTOR) if len(code_blocks) == 0: - logger.error('Code block selector {!r} returned no matches'.format( - CODE_BLOCK_SELECTOR)) + logger.error('Code block selector {!r} returned no matches'.format(CODE_BLOCK_SELECTOR)) return [block.get_text() for block in code_blocks] @@ -72,7 +71,8 @@ def get_test(text): for test_str in test_strs: test = get_test(test_str) if test is None: - logger.error('Test/response pattern {!r} returned no matches from string {!r}'.format(TEST_PATTERN, test_str)) + logger.error( + 'Test/response pattern {!r} returned no matches from string {!r}'.format(TEST_PATTERN, test_str)) else: tests.append((test.group(1).strip(), test.group(2).strip())) return tests @@ -103,9 +103,11 @@ def _run_ocaml_code(code): def equivalent(text): return text + def strip_whitespace(text): return text.strip() + def normalize_whitespace(text): """Replace instances of whitespace with ' '. @@ -229,7 +231,9 @@ def main(): if not args.url: url_guess = infer_url(FILE) if not url_guess: # break if filename can't be matched - logger.critical('Could not infer url from filename {!r}. Try passing a url manually with the `--url` flag.'.format(FILE)) + logger.critical(( + 'Could not infer url from filename {!r}. ' + 'Try passing a url manually with the `--url` flag.').format(FILE)) sys.exit(1) else: URL = url_guess @@ -266,7 +270,8 @@ def main(): # TODO: get titles/descriptions from code blocks blocks = get_blocks(html) # parse code blocks for tests - test_suites = list(enumerate(filter(None, (get_tests(b) for b in blocks)), 1)) # list of suites and indices (starting at 1) (skipping empty suites) + # list of suites and indices (starting at 1) (skipping empty suites) + test_suites = list(enumerate(filter(None, (get_tests(b) for b in blocks)), 1)) num_tests = sum([len(suite) for j, suite in test_suites]) logger.info("Found {} test suites and {} tests total".format( len(test_suites), num_tests)) @@ -300,7 +305,7 @@ def main(): res = run_test(test, expected_output, file=FILE) header_temp = ' test {} of {} in suite {}'.format(k+1, len(suite), j) if res is None: # skip unparsable texts - print(colored('Skipped'+header_temp+': Unable to parse output','yellow')) + print(colored('Skipped'+header_temp+': Unable to parse output', 'yellow')) continue else: result, output, method = res diff --git a/setup.cfg b/setup.cfg index 7da1f96..6deafc2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [flake8] -max-line-length = 100 +max-line-length = 120 diff --git a/testfocstest.py b/testfocstest.py index 4f2c5ad..e2ae0cc 100644 --- a/testfocstest.py +++ b/testfocstest.py @@ -3,7 +3,7 @@ import doctest import focstest -from focstest import equivalent, strip_whitespace, normalize_whitespace +from focstest import normalize_whitespace def load_tests(loader, tests, ignore): @@ -20,7 +20,7 @@ def test_normalize_whitespace(self): # add examples here in the format (expected output, generated output) cases = [ ('- : int list =\n[19; 58; 29; 88; 44; 22; 11; 34; 17; 52; 26; 13; 40; 20; 10; 5; 16; 8; 4; 2; 1]', - '- : int list =\n[19; 58; 29; 88; 44; 22; 11; 34; 17; 52; 26; 13; 40; 20; 10; 5; 16; 8; 4; 2;\n 1]\n') + '- : int list =\n[19; 58; 29; 88; 44; 22; 11; 34; 17; 52; 26; 13; 40; 20; 10; 5; 16; 8; 4; 2;\n 1]\n') ] for expected, generated in cases: self.assertEqual(