diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-test.yml similarity index 62% rename from .github/workflows/python-package.yml rename to .github/workflows/python-test.yml index ffe3f57..e7fc00a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-test.yml @@ -1,31 +1,36 @@ -name: Python Package -on: [push, pull_request] +name: Python Test +on: + push: + branches: [main] + pull_request: + branches: [main] jobs: build-linux: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -e .[dev] - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Test with pytest + - name: Test with tox run: | - cd tests - pytest . + tox run + env: + TOX_GH_MAJOR_MINOR: ${{ matrix.python-version }} - name: Test parallel execution run: | cd integration-tests/parallelization bash time-parallel-tests.sh . - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v2.0.0-beta.2 + uses: EnricoMi/publish-unit-test-result-action@v2 diff --git a/LICENSE b/LICENSE index 8a1111b..528d81d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Inline Tests Dev Team +Copyright (c) 2022-present Inline Tests Dev Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/demo/example.py b/demo/example.py index ee99c0c..63f1894 100644 --- a/demo/example.py +++ b/demo/example.py @@ -3,33 +3,34 @@ import re from inline import itest + def get_assignment_map_from_checkpoint(tvars, init_checkpoint): - """Compute the union of the current variables and checkpoint variables.""" - assignment_map = {} - initialized_variable_names = {} + """Compute the union of the current variables and checkpoint variables.""" + assignment_map = {} + initialized_variable_names = {} - name_to_variable = collections.OrderedDict() - for var in tvars: - name = var.name - # statement under test - m = re.match("^(.*):\\d+$", name) - # inline test - itest().given(name, "a:0").check_eq(m.group(1), "a") - # a failing inline test - # itest().given(name, "a:0").check_eq(m.group(1), "aaa") - if m is not None: - name = m.group(1) - name_to_variable[name] = var + name_to_variable = collections.OrderedDict() + for var in tvars: + name = var.name + # statement under test + m = re.match("^(.*):\\d+$", name) + # inline test + itest().given(name, "a:0").check_eq(m.group(1), "a") + # a failing inline test + # itest().given(name, "a:0").check_eq(m.group(1), "aaa") + if m is not None: + name = m.group(1) + name_to_variable[name] = var - init_vars = tf.train.list_variables(init_checkpoint) + init_vars = tf.train.list_variables(init_checkpoint) - assignment_map = collections.OrderedDict() - for x in init_vars: - (name, var) = (x[0], x[1]) - if name not in name_to_variable: - continue - assignment_map[name] = name - initialized_variable_names[name] = 1 - initialized_variable_names[name + ":0"] = 1 + assignment_map = collections.OrderedDict() + for x in init_vars: + (name, var) = (x[0], x[1]) + if name not in name_to_variable: + continue + assignment_map[name] = name + initialized_variable_names[name] = 1 + initialized_variable_names[name + ":0"] = 1 - return (assignment_map, initialized_variable_names) + return (assignment_map, initialized_variable_names) diff --git a/demo/features.py b/demo/features.py index 3343a10..ae08656 100644 --- a/demo/features.py +++ b/demo/features.py @@ -3,6 +3,7 @@ ### display name: customize test name, default name is filename+line number + def inline_test_with_name(a): b = a + 1 itest(test_name="test-with-name").given(a, 1).check_eq(b, 2) @@ -10,6 +11,7 @@ def inline_test_with_name(a): ### parameterized tests: pass different sets of inputs to tests + def inline_test_parameterized(a): b = a + 1 itest(parameterized=True).given(a, [1, 2, 3]).check_eq(b, [2, 3, 4]) @@ -17,6 +19,7 @@ def inline_test_parameterized(a): ### repeated tests: repeat a test a specified number of times + def inline_test_repeated(a): b = a + 1 itest(repeated=3).given(a, 1).check_eq(b, 2) @@ -24,6 +27,7 @@ def inline_test_repeated(a): ### disabled tests: disable a test + def inline_test_disabled(a): b = a + 1 itest(disabled=True).given(a, 1).check_eq(b, "this test is disabled") @@ -31,11 +35,13 @@ def inline_test_disabled(a): ### timeout: fail a test if the execution time exceeds a given duration + def slow_method(): time.sleep(0.01) # time.sleep(0.1) return 1 + def inline_test_with_timeout(a): b = a + 1 # this inline test will fail if you increase the sleep time in slow_method @@ -44,6 +50,7 @@ def inline_test_with_timeout(a): ### assumptions: execute test when the assumption is satisfied + def inline_test_with_assume(a): b = a + 1 itest().assume(False).given(a, 1).check_eq(b, 2) @@ -51,6 +58,7 @@ def inline_test_with_assume(a): ### more assertions + def inline_test_assertions(a): b = a + 1 itest().given(a, 1).check_neq(b, 1) @@ -67,6 +75,7 @@ def inline_test_assertions(a): ### tagged tests: tag tests for filtering ### run tests in order: Run some tests first + def inline_test_with_tags(a): b = a + 1 itest(test_name="foo1", tag=["foo"]).given(a, 1).check_eq(b, 2) diff --git a/demo/parallel/a.py b/demo/parallel/a.py index 5b58713..7bfed40 100644 --- a/demo/parallel/a.py +++ b/demo/parallel/a.py @@ -2,14 +2,14 @@ a = 0 a = a + 1 -itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) -itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) -itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) +itest("1", tag=["add"]).given(a, 1).check_eq(a, 2) +itest("1", tag=["add"]).given(a, 1).check_eq(a, 2) +itest("1", tag=["add"]).given(a, 1).check_eq(a, 2) a = a + 2 itest("2").given(a, 1).check_eq(a, 3) itest("2").given(a, 1).check_eq(a, 3) itest("2").given(a, 1).check_eq(a, 3) a = a - 1 -itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) -itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) -itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) +itest("3", tag=["minus"]).given(a, 1).check_eq(a, 0) +itest("3", tag=["minus"]).given(a, 1).check_eq(a, 0) +itest("3", tag=["minus"]).given(a, 1).check_eq(a, 0) diff --git a/demo/parallel/b.py b/demo/parallel/b.py index 4f9b317..4c0f465 100644 --- a/demo/parallel/b.py +++ b/demo/parallel/b.py @@ -4,14 +4,14 @@ sleep = 0.1 b = 0 b = b + 1 -itest("1", tag = ["add"]).given(b, 1).check_eq(b, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(b, 1).check_eq(b, 2) -itest("1", tag = ["add"]).given(b, 1).check_eq(b, 2) +itest("1", tag=["add"]).given(b, 1).check_eq(b, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(b, 1).check_eq(b, 2) +itest("1", tag=["add"]).given(b, 1).check_eq(b, 2) b = b + 2 itest("2").given(b, 1).check_eq(b, 3).check_eq(time.sleep(sleep), None) itest("2").given(b, 1).check_eq(b, 3) itest("2").given(b, 1).check_eq(b, 3) b = b - 1 -itest("3", tag = ["minus"]).given(b, 1).check_eq(b, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(b, 1).check_eq(b, 0) -itest("3", tag = ["minus"]).given(b, 1).check_eq(b, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(b, 1).check_eq(b, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(b, 1).check_eq(b, 0) +itest("3", tag=["minus"]).given(b, 1).check_eq(b, 0) diff --git a/demo/parallel/c.py b/demo/parallel/c.py index 61eed59..4a0f4d7 100644 --- a/demo/parallel/c.py +++ b/demo/parallel/c.py @@ -4,14 +4,14 @@ sleep = 0.2 c = 0 c = c + 1 -itest("1", tag = ["add"]).given(c, 1).check_eq(c, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(c, 1).check_eq(c, 2) -itest("1", tag = ["add"]).given(c, 1).check_eq(c, 2) +itest("1", tag=["add"]).given(c, 1).check_eq(c, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(c, 1).check_eq(c, 2) +itest("1", tag=["add"]).given(c, 1).check_eq(c, 2) c = c + 2 itest("2").given(c, 1).check_eq(c, 3).check_eq(time.sleep(sleep), None) itest("2").given(c, 1).check_eq(c, 3) itest("2").given(c, 1).check_eq(c, 3) c = c - 1 -itest("3", tag = ["minus"]).given(c, 1).check_eq(c, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(c, 1) -itest("3", tag = ["minus"]).given(c, 1) \ No newline at end of file +itest("3", tag=["minus"]).given(c, 1).check_eq(c, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(c, 1) +itest("3", tag=["minus"]).given(c, 1) diff --git a/demo/parallel/d.py b/demo/parallel/d.py index a1c3666..8e67c5d 100644 --- a/demo/parallel/d.py +++ b/demo/parallel/d.py @@ -4,14 +4,14 @@ sleep = 0.1 d = 0 d = d + 1 -itest("1", tag = ["add"]).given(d, 1).check_eq(d, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(d, 1).check_eq(d, 2) -itest("1", tag = ["add"]).given(d, 1).check_eq(d, 2) +itest("1", tag=["add"]).given(d, 1).check_eq(d, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(d, 1).check_eq(d, 2) +itest("1", tag=["add"]).given(d, 1).check_eq(d, 2) d = d + 2 itest("2").given(d, 1).check_eq(d, 3).check_eq(time.sleep(sleep), None) itest("2").given(d, 1).check_eq(d, 3) itest("2").given(d, 1).check_eq(d, 3) d = d - 1 -itest("3", tag = ["minus"]).given(d, 1).check_eq(d, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(d, 1).check_eq(d, 0) -itest("3", tag = ["minus"]).given(d, 1).check_eq(d, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(d, 1).check_eq(d, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(d, 1).check_eq(d, 0) +itest("3", tag=["minus"]).given(d, 1).check_eq(d, 0) diff --git a/integration-tests/parallelization/test_files/a.py b/integration-tests/parallelization/test_files/a.py index 5b58713..7bfed40 100644 --- a/integration-tests/parallelization/test_files/a.py +++ b/integration-tests/parallelization/test_files/a.py @@ -2,14 +2,14 @@ a = 0 a = a + 1 -itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) -itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) -itest("1", tag = ["add"]).given(a, 1).check_eq(a, 2) +itest("1", tag=["add"]).given(a, 1).check_eq(a, 2) +itest("1", tag=["add"]).given(a, 1).check_eq(a, 2) +itest("1", tag=["add"]).given(a, 1).check_eq(a, 2) a = a + 2 itest("2").given(a, 1).check_eq(a, 3) itest("2").given(a, 1).check_eq(a, 3) itest("2").given(a, 1).check_eq(a, 3) a = a - 1 -itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) -itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) -itest("3", tag = ["minus"]).given(a, 1).check_eq(a, 0) +itest("3", tag=["minus"]).given(a, 1).check_eq(a, 0) +itest("3", tag=["minus"]).given(a, 1).check_eq(a, 0) +itest("3", tag=["minus"]).given(a, 1).check_eq(a, 0) diff --git a/integration-tests/parallelization/test_files/b.py b/integration-tests/parallelization/test_files/b.py index 4e14f9b..4215938 100644 --- a/integration-tests/parallelization/test_files/b.py +++ b/integration-tests/parallelization/test_files/b.py @@ -4,14 +4,14 @@ sleep = 1 b = 0 b = b + 1 -itest("1", tag = ["add"]).given(b, 1).check_eq(b, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(b, 1).check_eq(b, 2) -itest("1", tag = ["add"]).given(b, 1).check_eq(b, 2) +itest("1", tag=["add"]).given(b, 1).check_eq(b, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(b, 1).check_eq(b, 2) +itest("1", tag=["add"]).given(b, 1).check_eq(b, 2) b = b + 2 itest("2").given(b, 1).check_eq(b, 3).check_eq(time.sleep(sleep), None) itest("2").given(b, 1).check_eq(b, 3) itest("2").given(b, 1).check_eq(b, 3) b = b - 1 -itest("3", tag = ["minus"]).given(b, 1).check_eq(b, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(b, 1).check_eq(b, 0) -itest("3", tag = ["minus"]).given(b, 1).check_eq(b, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(b, 1).check_eq(b, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(b, 1).check_eq(b, 0) +itest("3", tag=["minus"]).given(b, 1).check_eq(b, 0) diff --git a/integration-tests/parallelization/test_files/c.py b/integration-tests/parallelization/test_files/c.py index 027ca63..eae9b93 100644 --- a/integration-tests/parallelization/test_files/c.py +++ b/integration-tests/parallelization/test_files/c.py @@ -4,14 +4,14 @@ sleep = 2 c = 0 c = c + 1 -itest("1", tag = ["add"]).given(c, 1).check_eq(c, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(c, 1).check_eq(c, 2) -itest("1", tag = ["add"]).given(c, 1).check_eq(c, 2) +itest("1", tag=["add"]).given(c, 1).check_eq(c, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(c, 1).check_eq(c, 2) +itest("1", tag=["add"]).given(c, 1).check_eq(c, 2) c = c + 2 itest("2").given(c, 1).check_eq(c, 3).check_eq(time.sleep(sleep), None) itest("2").given(c, 1).check_eq(c, 3) itest("2").given(c, 1).check_eq(c, 3) c = c - 1 -itest("3", tag = ["minus"]).given(c, 1).check_eq(c, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(c, 1) -itest("3", tag = ["minus"]).given(c, 1) \ No newline at end of file +itest("3", tag=["minus"]).given(c, 1).check_eq(c, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(c, 1) +itest("3", tag=["minus"]).given(c, 1) diff --git a/integration-tests/parallelization/test_files/d.py b/integration-tests/parallelization/test_files/d.py index f8463a1..85ac2bb 100644 --- a/integration-tests/parallelization/test_files/d.py +++ b/integration-tests/parallelization/test_files/d.py @@ -4,14 +4,14 @@ sleep = 1 d = 0 d = d + 1 -itest("1", tag = ["add"]).given(d, 1).check_eq(d, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(d, 1).check_eq(d, 2) -itest("1", tag = ["add"]).given(d, 1).check_eq(d, 2) +itest("1", tag=["add"]).given(d, 1).check_eq(d, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(d, 1).check_eq(d, 2) +itest("1", tag=["add"]).given(d, 1).check_eq(d, 2) d = d + 2 itest("2").given(d, 1).check_eq(d, 3).check_eq(time.sleep(sleep), None) itest("2").given(d, 1).check_eq(d, 3) itest("2").given(d, 1).check_eq(d, 3) d = d - 1 -itest("3", tag = ["minus"]).given(d, 1).check_eq(d, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(d, 1).check_eq(d, 0) -itest("3", tag = ["minus"]).given(d, 1).check_eq(d, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(d, 1).check_eq(d, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(d, 1).check_eq(d, 0) +itest("3", tag=["minus"]).given(d, 1).check_eq(d, 0) diff --git a/integration-tests/parallelization/test_files/e.py b/integration-tests/parallelization/test_files/e.py index 235c9fa..612bd9a 100644 --- a/integration-tests/parallelization/test_files/e.py +++ b/integration-tests/parallelization/test_files/e.py @@ -4,14 +4,14 @@ sleep = 2 e = 0 e = e + 1 -itest("1", tag = ["add"]).given(e, 1).check_eq(e, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(e, 1).check_eq(e, 2) -itest("1", tag = ["add"]).given(e, 1).check_eq(e, 2) +itest("1", tag=["add"]).given(e, 1).check_eq(e, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(e, 1).check_eq(e, 2) +itest("1", tag=["add"]).given(e, 1).check_eq(e, 2) e = e + 2 itest("2").given(e, 1).check_eq(e, 3).check_eq(time.sleep(sleep), None) itest("2").given(e, 1).check_eq(e, 3) itest("2").given(e, 1).check_eq(e, 3) e = e - 1 -itest("3", tag = ["minus"]).given(e, 1).check_eq(e, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(e, 1).check_eq(e, 0) -itest("3", tag = ["minus"]).given(e, 1).check_eq(e, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(e, 1).check_eq(e, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(e, 1).check_eq(e, 0) +itest("3", tag=["minus"]).given(e, 1).check_eq(e, 0) diff --git a/integration-tests/parallelization/test_files/f.py b/integration-tests/parallelization/test_files/f.py index 221835d..89dff56 100644 --- a/integration-tests/parallelization/test_files/f.py +++ b/integration-tests/parallelization/test_files/f.py @@ -4,14 +4,14 @@ sleep = 1 f = 0 f = f + 1 -itest("1", tag = ["add"]).given(f, 1).check_eq(f, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(f, 1).check_eq(f, 2) -itest("1", tag = ["add"]).given(f, 1).check_eq(f, 2) +itest("1", tag=["add"]).given(f, 1).check_eq(f, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(f, 1).check_eq(f, 2) +itest("1", tag=["add"]).given(f, 1).check_eq(f, 2) f = f + 2 itest("2").given(f, 1).check_eq(f, 3).check_eq(time.sleep(sleep), None) itest("2").given(f, 1).check_eq(f, 3) itest("2").given(f, 1).check_eq(f, 3) f = f - 1 -itest("3", tag = ["minus"]).given(f, 1).check_eq(f, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(f, 1).check_eq(f, 0) -itest("3", tag = ["minus"]).given(f, 1).check_eq(f, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(f, 1).check_eq(f, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(f, 1).check_eq(f, 0) +itest("3", tag=["minus"]).given(f, 1).check_eq(f, 0) diff --git a/integration-tests/parallelization/test_files/g.py b/integration-tests/parallelization/test_files/g.py index a7b5127..de88e1b 100644 --- a/integration-tests/parallelization/test_files/g.py +++ b/integration-tests/parallelization/test_files/g.py @@ -4,14 +4,14 @@ sleep = 2 g = 0 g = g + 1 -itest("1", tag = ["add"]).given(g, 1).check_eq(g, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(g, 1).check_eq(g, 2) -itest("1", tag = ["add"]).given(g, 1).check_eq(g, 2) +itest("1", tag=["add"]).given(g, 1).check_eq(g, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(g, 1).check_eq(g, 2) +itest("1", tag=["add"]).given(g, 1).check_eq(g, 2) g = g + 2 itest("2").given(g, 1).check_eq(g, 3).check_eq(time.sleep(sleep), None) itest("2").given(g, 1).check_eq(g, 3) itest("2").given(g, 1).check_eq(g, 3) g = g - 1 -itest("3", tag = ["minus"]).given(g, 1).check_eq(g, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(g, 1).check_eq(g, 0) -itest("3", tag = ["minus"]).given(g, 1).check_eq(g, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(g, 1).check_eq(g, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(g, 1).check_eq(g, 0) +itest("3", tag=["minus"]).given(g, 1).check_eq(g, 0) diff --git a/integration-tests/parallelization/test_files/h.py b/integration-tests/parallelization/test_files/h.py index e28003c..706e13b 100644 --- a/integration-tests/parallelization/test_files/h.py +++ b/integration-tests/parallelization/test_files/h.py @@ -4,14 +4,14 @@ sleep = 1 h = 0 h = h + 1 -itest("1", tag = ["add"]).given(h, 1).check_eq(h, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(h, 1).check_eq(h, 2) -itest("1", tag = ["add"]).given(h, 1).check_eq(h, 2) +itest("1", tag=["add"]).given(h, 1).check_eq(h, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(h, 1).check_eq(h, 2) +itest("1", tag=["add"]).given(h, 1).check_eq(h, 2) h = h + 2 itest("2").given(h, 1).check_eq(h, 3).check_eq(time.sleep(sleep), None) itest("2").given(h, 1).check_eq(h, 3) itest("2").given(h, 1).check_eq(h, 3) h = h - 1 -itest("3", tag = ["minus"]).given(h, 1).check_eq(h, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(h, 1).check_eq(h, 0) -itest("3", tag = ["minus"]).given(h, 1).check_eq(h, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(h, 1).check_eq(h, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(h, 1).check_eq(h, 0) +itest("3", tag=["minus"]).given(h, 1).check_eq(h, 0) diff --git a/integration-tests/parallelization/test_files/i.py b/integration-tests/parallelization/test_files/i.py index f4669d8..5c4cd32 100644 --- a/integration-tests/parallelization/test_files/i.py +++ b/integration-tests/parallelization/test_files/i.py @@ -4,14 +4,14 @@ sleep = 2 i = 0 i = i + 1 -itest("1", tag = ["add"]).given(i, 1).check_eq(i, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(i, 1).check_eq(i, 2) -itest("1", tag = ["add"]).given(i, 1).check_eq(i, 2) +itest("1", tag=["add"]).given(i, 1).check_eq(i, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(i, 1).check_eq(i, 2) +itest("1", tag=["add"]).given(i, 1).check_eq(i, 2) i = i + 2 itest("2").given(i, 1).check_eq(i, 3).check_eq(time.sleep(sleep), None) itest("2").given(i, 1).check_eq(i, 3) itest("2").given(i, 1).check_eq(i, 3) i = i - 1 -itest("3", tag = ["minus"]).given(i, 1).check_eq(i, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(i, 1).check_eq(i, 0) -itest("3", tag = ["minus"]).given(i, 1).check_eq(i, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(i, 1).check_eq(i, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(i, 1).check_eq(i, 0) +itest("3", tag=["minus"]).given(i, 1).check_eq(i, 0) diff --git a/integration-tests/parallelization/test_files/j.py b/integration-tests/parallelization/test_files/j.py index b0ae86c..5b0b832 100644 --- a/integration-tests/parallelization/test_files/j.py +++ b/integration-tests/parallelization/test_files/j.py @@ -4,14 +4,14 @@ sleep = 1 j = 0 j = j + 1 -itest("1", tag = ["add"]).given(j, 1).check_eq(j, 2).check_eq(time.sleep(sleep), None) -itest("1", tag = ["add"]).given(j, 1).check_eq(j, 2) -itest("1", tag = ["add"]).given(j, 1).check_eq(j, 2) +itest("1", tag=["add"]).given(j, 1).check_eq(j, 2).check_eq(time.sleep(sleep), None) +itest("1", tag=["add"]).given(j, 1).check_eq(j, 2) +itest("1", tag=["add"]).given(j, 1).check_eq(j, 2) j = j + 2 itest("2").given(j, 1).check_eq(j, 3).check_eq(time.sleep(sleep), None) itest("2").given(j, 1).check_eq(j, 3) itest("2").given(j, 1).check_eq(j, 3) j = j - 1 -itest("3", tag = ["minus"]).given(j, 1).check_eq(j, 0).check_eq(time.sleep(sleep), None) -itest("3", tag = ["minus"]).given(j, 1).check_eq(j, 0) -itest("3", tag = ["minus"]).given(j, 1).check_eq(j, 0) \ No newline at end of file +itest("3", tag=["minus"]).given(j, 1).check_eq(j, 0).check_eq(time.sleep(sleep), None) +itest("3", tag=["minus"]).given(j, 1).check_eq(j, 0) +itest("3", tag=["minus"]).given(j, 1).check_eq(j, 0) diff --git a/pyproject.toml b/pyproject.toml index 7fd26b9..8350712 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,88 @@ [build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" \ No newline at end of file +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "inline" +dynamic = ["version"] +description = "A pytest plugin for writing inline tests" +readme = "README.md" +requires-python = ">=3.7" +license = { file = "LICENSE" } +authors = [{ name = "Yu Liu", email = "im.yukiliu@gmail.com" }] +maintainers = [ + { name = "Yu Liu", email = "im.yukiliu@gmail.com" }, + { name = "Pengyu Nie", email = "prodigy.sov@gmail.com" }, + { name = "Zachary William Thurston", email = "zwt3@cornell.edu" }, + { name = "Alan Han", email = "ayh9@cornell.edu" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Framework :: Pytest", + "Intended Audience :: Developers", + "Topic :: Software Development :: Testing", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Operating System :: OS Independent", + "License :: OSI Approved :: MIT License", +] +dependencies = ["pytest>=7.0,<9.0"] + +[project.optional-dependencies] +dev = ["hatch", "coverage[toml]", "black", "ruff", "tox", "tox-gh"] + +[project.urls] +Issues = "https://github.com/pytest-dev/pytest-inline/issues" +Source = "https://github.com/pytest-dev/pytest-inline" + +[project.entry-points.pytest11] +inline = "inline.plugin" + +[tool.hatch.version] +path = "src/inline/__about__.py" + +# testing (pytest) +[tool.pytest.ini_options] +addopts = "-p pytester" +testpaths = ["tests"] + +# code coverage (coverage) +[tool.coverage.run] +source_pkgs = ["inline", "tests"] +branch = true +parallel = true +omit = ["src/inline/__about__.py"] + +[tool.coverage.paths] +inline = ["src/inline", "*/inline/src/inline"] +tests = ["tests", "*/inline/tests"] + +[tool.coverage.report] +exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"] + +# formatting (black) +[tool.black] +target-version = ["py38"] +line-length = 120 + +# linting (ruff) +[tool.ruff] +target-version = "py38" +line-length = 120 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle error + "F", # pyflakes + "I", # isort +] + +[tool.ruff.lint.isort] +known-first-party = ["inline"] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 569eeb7..0000000 --- a/pytest.ini +++ /dev/null @@ -1,5 +0,0 @@ -[pytest] -addopts = - -p pytester -testpaths = - tests \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 3ddfa4a..0000000 --- a/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import codecs -import os - -from setuptools import setup -from setuptools import find_packages - - -def read(fname): - file_path = os.path.join(os.path.dirname(__file__), fname) - return codecs.open(file_path, encoding="utf-8").read() - - -setup( - name="pytest-inline", - version="1.0.5", - author="Yu Liu", - author_email="yuki.liu@utexas.edu", - maintainer="Alan Han, Yu Liu, Pengyu Nie, Zachary William Thurston", - maintainer_email="yuki.liu@utexas.edu", - license="MIT", - url="https://github.com/pytest-dev/pytest-inline", - description="A pytest plugin for writing inline tests.", - long_description_content_type="text/markdown", - long_description=read("README.md"), - packages=find_packages(where="src"), - package_dir={"": "src"}, - python_requires=">=3.7", - install_requires=["pytest>=7.0.0"], - classifiers=[ - "Development Status :: 4 - Beta", - "Framework :: Pytest", - "Intended Audience :: Developers", - "Topic :: Software Development :: Testing", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Operating System :: OS Independent", - "License :: OSI Approved :: MIT License", - ], - entry_points={ - "pytest11": [ - "inline = inline.plugin", - ], - }, - extras_require={ - "dev": ["flake8", "black", "tox"], - }, -) diff --git a/src/inline/__about__.py b/src/inline/__about__.py new file mode 100644 index 0000000..8a20265 --- /dev/null +++ b/src/inline/__about__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2022-present Inline Tests Dev Team +# +# SPDX-License-Identifier: MIT +__version__ = "1.0.5" diff --git a/src/inline/ast_future.py b/src/inline/ast_future.py index e3faa0a..8143964 100644 --- a/src/inline/ast_future.py +++ b/src/inline/ast_future.py @@ -169,10 +169,7 @@ def get_raw_docstring(self, node): return that docstring node, None otherwise. Logic mirrored from ``_PyAST_GetDocString``.""" - if ( - not isinstance(node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)) - or len(node.body) < 1 - ): + if not isinstance(node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)) or len(node.body) < 1: return None node = node.body[0] if not isinstance(node, Expr): @@ -212,9 +209,7 @@ def _write_docstring_and_traverse_body(self, node): self.traverse(node.body) def visit_Module(self, node): - self._type_ignores = { - ignore.lineno: f"ignore{ignore.tag}" for ignore in node.type_ignores - } + self._type_ignores = {ignore.lineno: f"ignore{ignore.tag}" for ignore in node.type_ignores} self._write_docstring_and_traverse_body(node) self._type_ignores.clear() @@ -267,9 +262,7 @@ def visit_AugAssign(self, node): def visit_AnnAssign(self, node): self.fill() - with self.delimit_if( - "(", ")", not node.simple and isinstance(node.target, Name) - ): + with self.delimit_if("(", ")", not node.simple and isinstance(node.target, Name)): self.traverse(node.target) self.write(": ") self.traverse(node.annotation) @@ -476,9 +469,7 @@ def visit_AsyncWith(self, node): with self.block(extra=self.get_type_comment(node)): self.traverse(node.body) - def _str_literal_helper( - self, string, *, quote_types=_ALL_QUOTES, escape_special_whitespace=False - ): + def _str_literal_helper(self, string, *, quote_types=_ALL_QUOTES, escape_special_whitespace=False): """Helper for writing string literals, minimizing escapes. Returns the tuple (string literal to write, possible quote types). """ @@ -601,11 +592,7 @@ def _write_constant(self, value): if isinstance(value, (float, complex)): # Substitute overflowing decimal literal for AST infinities, # and inf - inf for NaNs. - self.write( - repr(value) - .replace("inf", _INFSTR) - .replace("nan", f"({_INFSTR}-{_INFSTR})") - ) + self.write(repr(value).replace("inf", _INFSTR).replace("nan", f"({_INFSTR}-{_INFSTR})")) elif self._avoid_backslashes and isinstance(value, str): self._write_str_avoiding_backslashes(value) else: @@ -704,9 +691,7 @@ def write_item(item): write_key_value_pair(k, v) with self.delimit("{", "}"): - self.interleave( - lambda: self.write(", "), write_item, zip(node.keys, node.values) - ) + self.interleave(lambda: self.write(", "), write_item, zip(node.keys, node.values)) def visit_Tuple(self, node): with self.delimit("(", ")"): @@ -819,9 +804,7 @@ def increasing_level_traverse(node): with self.require_parens(operator_precedence, node): s = f" {operator} " - self.interleave( - lambda: self.write(s), increasing_level_traverse, node.values - ) + self.interleave(lambda: self.write(s), increasing_level_traverse, node.values) def visit_Attribute(self, node): self.set_precedence(_Precedence.ATOM, node.value) diff --git a/src/inline/inline.py b/src/inline/inline.py index cdf7aaa..5766542 100644 --- a/src/inline/inline.py +++ b/src/inline/inline.py @@ -119,12 +119,12 @@ def check_not_same(self, actual_value, expected_value): def fail(self): """ Fails the test - + :returns: Inline object :raises: AssertionError """ - - def assume(self, condition:bool): + + def assume(self, condition: bool): """ Executes the test under the assuming the given condition is true. If supplied, must be supplied immediately after itest(). Can only supply 1 assume statement. @@ -133,6 +133,7 @@ def assume(self, condition:bool): :raises: AssertionError """ + class Group: def __init__(self, *arg): """ diff --git a/src/inline/plugin.py b/src/inline/plugin.py index 0ea94c3..11c0774 100644 --- a/src/inline/plugin.py +++ b/src/inline/plugin.py @@ -10,19 +10,41 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union import pytest -from _pytest.main import Session -from _pytest.pathlib import fnmatch_ex, import_path -from _pytest.python import Package -from pytest import Collector, Config, FixtureRequest, Parser +from _pytest.pathlib import fnmatch_ex +from pytest import Collector, Config, Parser if sys.version_info >= (3, 9, 0): from ast import unparse as ast_unparse else: from .ast_future import unparse as ast_unparse +if pytest.version_tuple >= (8, 0, 0): + # fixture API changed in pytest 8 + # https://github.com/pytest-dev/pytest/issues/11218 + from _pytest.fixtures import TopRequest # noqa: I001 + + # consider_namespace_packages is added as a required argument in pytest 8 + # https://github.com/pytest-dev/pytest/issues/11475 + from _pytest.pathlib import import_path as _import_path # noqa: I001 + + def import_path(*args, **kwargs): + return _import_path(*args, **kwargs, consider_namespace_packages=False) + + # scope architecture changed in pytest 8 + # https://github.com/pytest-dev/pytest/issues/7777 + from _pytest.main import Session, Dir # noqa: I001 + from _pytest.python import Package # noqa: I001 + + HIGHLEVEL_SCOPES = (Session, Dir, Package) + +else: + from pytest import FixtureRequest # noqa: I001 + from _pytest.pathlib import import_path # noqa: I001 + from _pytest.main import Session # noqa: I001 + from _pytest.python import Package # noqa: I001 + + HIGHLEVEL_SCOPES = (Session, Package) -# Alternatively, invoke pytest with -p inline) -# pytest_plugins = ["inline"] # register argparse-style options and ini-file values, called once at the beginning of a test run def pytest_addoption(parser: Parser) -> None: @@ -82,9 +104,7 @@ def pytest_addoption(parser: Parser) -> None: @pytest.hookimpl() def pytest_exception_interact(node, call, report): - if isinstance(call.excinfo.value, MalformedException) or isinstance( - call.excinfo.value, AssertionError - ): + if isinstance(call.excinfo.value, MalformedException) or isinstance(call.excinfo.value, AssertionError): # fail to parse inline test # fail to execute inline test entry = report.longrepr.reprtraceback.reprentries[-1] @@ -100,14 +120,10 @@ def pytest_configure(config): @pytest.hookimpl() def pytest_collectstart(collector): - if not isinstance(collector, (Session, Package)): - if collector.config.getoption("inlinetest_only") and ( - not isinstance(collector, InlinetestModule) - ): + if not isinstance(collector, HIGHLEVEL_SCOPES): + if collector.config.getoption("inlinetest_only") and (not isinstance(collector, InlinetestModule)): collector.collect = lambda: [] # type: ignore[assignment] - if collector.config.getoption("inlinetest_disable") and isinstance( - collector, InlinetestModule - ): + if collector.config.getoption("inlinetest_disable") and isinstance(collector, InlinetestModule): collector.collect = lambda: [] # type: ignore[assignment] @@ -162,16 +178,11 @@ def to_test(self): if self.assume_stmts == []: return "\n".join( [ExtractInlineTest.node_to_source_code(n) for n in self.given_stmts] - + [ - ExtractInlineTest.node_to_source_code(n) - for n in self.check_stmts - ] + + [ExtractInlineTest.node_to_source_code(n) for n in self.check_stmts] ) else: body_nodes = ( - [n for n in self.given_stmts] - + [n for n in self.previous_stmts] - + [n for n in self.check_stmts] + [n for n in self.given_stmts] + [n for n in self.previous_stmts] + [n for n in self.check_stmts] ) assume_statement = self.assume_stmts[0] assume_node = self.build_assume_node(assume_statement, body_nodes) @@ -181,20 +192,12 @@ def to_test(self): if self.assume_stmts is None or self.assume_stmts == []: return "\n".join( [ExtractInlineTest.node_to_source_code(n) for n in self.given_stmts] - + [ - ExtractInlineTest.node_to_source_code(n) - for n in self.previous_stmts - ] - + [ - ExtractInlineTest.node_to_source_code(n) - for n in self.check_stmts - ] + + [ExtractInlineTest.node_to_source_code(n) for n in self.previous_stmts] + + [ExtractInlineTest.node_to_source_code(n) for n in self.check_stmts] ) else: body_nodes = ( - [n for n in self.given_stmts] - + [n for n in self.previous_stmts] - + [n for n in self.check_stmts] + [n for n in self.given_stmts] + [n for n in self.previous_stmts] + [n for n in self.check_stmts] ) assume_statement = self.assume_stmts[0] assume_node = self.build_assume_node(assume_statement, body_nodes) @@ -260,11 +263,7 @@ def parse(self, obj, globs: None): for child in ast.iter_child_nodes(node): child.parent = node if isinstance(child, ast.stmt): - node.children = ( - [child] - if not hasattr(node, "children") - else [child] + node.children - ) + node.children = [child] if not hasattr(node, "children") else [child] + node.children extract_inline_test = ExtractInlineTest() extract_inline_test.visit(tree) @@ -320,9 +319,7 @@ def visit_ImportFrom(self, node): return self.generic_visit(node) def find_condition_stmt(self, stmt_node): - if isinstance(stmt_node.parent, ast.If) or isinstance( - stmt_node.parent, ast.While - ): + if isinstance(stmt_node.parent, ast.If) or isinstance(stmt_node.parent, ast.While): self.cur_inline_test.prev_stmt_type = PrevStmtType.CondExpr return stmt_node.parent.test else: @@ -345,9 +342,7 @@ def find_previous_stmt(self, node): else: for i in range(1, len(stmt_node.parent.children) - index_stmt_node): prev_stmt_node = stmt_node.parent.children[index_stmt_node + i] - if isinstance( - prev_stmt_node.value, ast.Call - ) and self.is_inline_test_class(prev_stmt_node.value): + if isinstance(prev_stmt_node.value, ast.Call) and self.is_inline_test_class(prev_stmt_node.value): continue else: return prev_stmt_node @@ -373,48 +368,25 @@ def parse_constructor(self, node): if sys.version_info >= (3, 8, 0): for index, arg in enumerate(node.args): # check if "test_name" is a string - if ( - index == 0 - and isinstance(arg, ast.Constant) - and isinstance(arg.value, str) - ): + if index == 0 and isinstance(arg, ast.Constant) and isinstance(arg.value, str): # get the test name if exists self.cur_inline_test.test_name = arg.value # check if "parameterized" is a boolean - elif ( - index == 1 - and isinstance(arg, ast.Constant) - and isinstance(arg.value, bool) - ): + elif index == 1 and isinstance(arg, ast.Constant) and isinstance(arg.value, bool): self.cur_inline_test.parameterized = arg.value # check if "repeated" is a positive integer - elif ( - index == 2 - and isinstance(arg, ast.Constant) - and isinstance(arg.value, int) - ): + elif index == 2 and isinstance(arg, ast.Constant) and isinstance(arg.value, int): if arg.value <= 0: - raise MalformedException( - f"inline test: {self.arg_repeated_str} must be greater than 0" - ) + raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") self.cur_inline_test.repeated = arg.value elif index == 3 and isinstance(arg.value, ast.List): tags = [] for elt in arg.value.elts: - if not ( - isinstance(elt, ast.Constant) - and isinstance(elt.value, str) - ): - raise MalformedException( - f"tag can only be List of string" - ) + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException(f"tag can only be List of string") tags.append(elt.value) self.cur_inline_test.tag = tags - elif ( - index == 4 - and isinstance(arg, ast.Constant) - and isinstance(arg.value, bool) - ): + elif index == 4 and isinstance(arg, ast.Constant) and isinstance(arg.value, bool): self.cur_inline_test.disabled = arg.value elif ( index == 5 @@ -449,23 +421,14 @@ def parse_constructor(self, node): and isinstance(keyword.value.value, int) ): if keyword.value.value <= 0: - raise MalformedException( - f"inline test: {self.arg_repeated_str} must be greater than 0" - ) + raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") self.cur_inline_test.repeated = keyword.value.value # check if "tag" is a list of string - elif keyword.arg == self.arg_tag_str and isinstance( - keyword.value, ast.List - ): + elif keyword.arg == self.arg_tag_str and isinstance(keyword.value, ast.List): tags = [] for elt in keyword.value.elts: - if not ( - isinstance(elt, ast.Constant) - and isinstance(elt.value, str) - ): - raise MalformedException( - f"tag can only be List of string" - ) + if not (isinstance(elt, ast.Constant) and isinstance(elt.value, str)): + raise MalformedException(f"tag can only be List of string") tags.append(elt.value) self.cur_inline_test.tag = tags # check if "disabled" is a boolean @@ -479,15 +442,10 @@ def parse_constructor(self, node): elif ( keyword.arg == self.arg_timeout_str and isinstance(keyword.value, ast.Constant) - and ( - isinstance(keyword.value.value, float) - or isinstance(keyword.value.value, int) - ) + and (isinstance(keyword.value.value, float) or isinstance(keyword.value.value, int)) ): if keyword.value.value <= 0.0: - raise MalformedException( - f"inline test: {self.arg_timeout_str} must be greater than 0" - ) + raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") self.cur_inline_test.timeout = keyword.value.value else: raise MalformedException( @@ -496,60 +454,34 @@ def parse_constructor(self, node): else: for index, arg in enumerate(node.args): # check if "test_name" is a string - if ( - index == 0 - and isinstance(arg, ast.Str) - and isinstance(arg.s, str) - ): + if index == 0 and isinstance(arg, ast.Str) and isinstance(arg.s, str): # get the test name if exists self.cur_inline_test.test_name = arg.s # check if "parameterized" is a boolean - elif ( - index == 1 - and isinstance(arg, ast.NameConstant) - and isinstance(arg.value, bool) - ): + elif index == 1 and isinstance(arg, ast.NameConstant) and isinstance(arg.value, bool): self.cur_inline_test.parameterized = arg.value # check if "repeated" is a positive integer - elif ( - index == 2 - and isinstance(arg, ast.Num) - and isinstance(arg.n, int) - ): + elif index == 2 and isinstance(arg, ast.Num) and isinstance(arg.n, int): if arg.n <= 0.0: - raise MalformedException( - f"inline test: {self.arg_repeated_str} must be greater than 0" - ) + raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") self.cur_inline_test.repeated = arg.n # check if "tag" is a list of string elif index == 3 and isinstance(arg.value, ast.List): tags = [] for elt in arg.value.elts: - if not ( - isinstance(elt, ast.Str) and isinstance(elt.s, str) - ): - raise MalformedException( - f"tag can only be List of string" - ) + if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): + raise MalformedException(f"tag can only be List of string") tags.append(elt.s) self.cur_inline_test.tag = tags # check if "disabled" is a boolean - elif ( - index == 4 - and isinstance(arg, ast.NameConstant) - and isinstance(arg.value, bool) - ): + elif index == 4 and isinstance(arg, ast.NameConstant) and isinstance(arg.value, bool): self.cur_inline_test.disabled = arg.value # check if "timeout" is a positive int elif ( - index == 5 - and isinstance(arg, ast.Num) - and (isinstance(arg.n, float) or isinstance(arg.n, int)) + index == 5 and isinstance(arg, ast.Num) and (isinstance(arg.n, float) or isinstance(arg.n, int)) ): if arg.n <= 0.0: - raise MalformedException( - f"inline test: {self.arg_timeout_str} must be greater than 0" - ) + raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") self.cur_inline_test.timeout = arg.n else: raise MalformedException( @@ -578,22 +510,14 @@ def parse_constructor(self, node): and isinstance(keyword.value.n, int) ): if keyword.value.n <= 0.0: - raise MalformedException( - f"inline test: {self.arg_repeated_str} must be greater than 0" - ) + raise MalformedException(f"inline test: {self.arg_repeated_str} must be greater than 0") self.cur_inline_test.repeated = keyword.value.n # check if "tag" is a list of string - elif keyword.arg == self.arg_tag_str and isinstance( - keyword.value, ast.List - ): + elif keyword.arg == self.arg_tag_str and isinstance(keyword.value, ast.List): tags = [] for elt in keyword.value.elts: - if not ( - isinstance(elt, ast.Str) and isinstance(elt.s, str) - ): - raise MalformedException( - f"tag can only be List of string" - ) + if not (isinstance(elt, ast.Str) and isinstance(elt.s, str)): + raise MalformedException(f"tag can only be List of string") tags.append(elt.s) self.cur_inline_test.tag = tags # check if "disabled" is a boolean @@ -607,24 +531,17 @@ def parse_constructor(self, node): elif ( keyword.arg == self.arg_timeout_str and isinstance(keyword.value, ast.Num) - and ( - isinstance(keyword.value.n, float) - or isinstance(keyword.value.n, int) - ) + and (isinstance(keyword.value.n, float) or isinstance(keyword.value.n, int)) ): if keyword.value.n <= 0.0: - raise MalformedException( - f"inline test: {self.arg_timeout_str} must be greater than 0" - ) + raise MalformedException(f"inline test: {self.arg_timeout_str} must be greater than 0") self.cur_inline_test.timeout = keyword.value.n else: raise MalformedException( f"inline test: {self.class_name_str}() accepts {NUM_OF_ARGUMENTS} arguments. 'test_name' must be a string constant, 'parameterized' must be a boolean constant, 'repeated' must be a positive integer, 'tag' must be a list of string, 'timeout' must be a positive float" ) else: - raise MalformedException( - f"inline test: invalid {self.class_name_str}(), expected at most 3 args" - ) + raise MalformedException(f"inline test: invalid {self.class_name_str}(), expected at most 3 args") if not self.cur_inline_test.test_name: # by default, use lineno as test name @@ -634,13 +551,9 @@ def parse_constructor(self, node): def parameterized_inline_tests_init(self, node: ast.List): if not self.cur_inline_test.parameterized_inline_tests: - self.cur_inline_test.parameterized_inline_tests = [ - InlineTest() for _ in range(len(node.elts)) - ] + self.cur_inline_test.parameterized_inline_tests = [InlineTest() for _ in range(len(node.elts))] if len(node.elts) != len(self.cur_inline_test.parameterized_inline_tests): - raise MalformedException( - "inline test: parameterized tests must have the same number of test cases" - ) + raise MalformedException("inline test: parameterized tests must have the same number of test cases") def parse_given(self, node): if len(node.args) == 2: @@ -648,9 +561,7 @@ def parse_given(self, node): self.parameterized_inline_tests_init(node.args[1]) for index, value in enumerate(node.args[1].elts): assign_node = ast.Assign(targets=[node.args[0]], value=value) - self.cur_inline_test.parameterized_inline_tests[ - index - ].given_stmts.append(assign_node) + self.cur_inline_test.parameterized_inline_tests[index].given_stmts.append(assign_node) else: assign_node = ast.Assign(targets=[node.args[0]], value=node.args[1]) self.cur_inline_test.given_stmts.append(assign_node) @@ -664,16 +575,12 @@ def parse_assume(self, node): for index, value in enumerate(node.args[0].elts): test_node = self.parse_group(value) assumption_node = self.build_assume(test_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].assume_stmts.append(assumption_node) + self.cur_inline_test.parameterized_inline_tests[index].assume_stmts.append(assumption_node) else: test_node = self.parse_group(node.args[0]) self.cur_inline_test.assume_stmts.append(test_node) else: - raise MalformedException( - "inline test: invalid assume() call, expected 1 arg" - ) + raise MalformedException("inline test: invalid assume() call, expected 1 arg") def build_assert_eq(self, left_node, comparator_node): equal_node = ast.Compare( @@ -709,9 +616,7 @@ def parse_check_eq(self, node): for index, value in enumerate(node.args[1].elts): comparator_node = self.parse_group(value) assert_node = self.build_assert_eq(left_node, comparator_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].check_stmts.append(assert_node) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) else: comparator_node = self.parse_group(node.args[1]) assert_node = self.build_assert_eq(left_node, comparator_node) @@ -724,9 +629,7 @@ def build_assert_true(self, test_node): test=test_node, msg=ast.Call( func=ast.Attribute( - ast.Constant( - "bool({0}) is True\nActual: bool({1}) is False\nExpected: bool({1}) is True\n" - ), + ast.Constant("bool({0}) is True\nActual: bool({1}) is False\nExpected: bool({1}) is True\n"), "format", ast.Load(), ), @@ -746,26 +649,20 @@ def parse_check_true(self, node): for index, value in enumerate(node.args[0].elts): test_node = self.parse_group(value) assert_node = self.build_assert_true(test_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].check_stmts.append(assert_node) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) else: test_node = self.parse_group(node.args[0]) assert_node = self.build_assert_true(test_node) self.cur_inline_test.check_stmts.append(assert_node) else: - raise MalformedException( - "inline test: invalid check_true(), expected 1 arg" - ) + raise MalformedException("inline test: invalid check_true(), expected 1 arg") def build_assert_false(self, operand_node): assert_node = ast.Assert( test=ast.UnaryOp(op=ast.Not(), operand=operand_node), msg=ast.Call( func=ast.Attribute( - ast.Constant( - "bool({0}) is False\nActual: bool({1}) is True\nExpected: bool({1}) is False\n" - ), + ast.Constant("bool({0}) is False\nActual: bool({1}) is True\nExpected: bool({1}) is False\n"), "format", ast.Load(), ), @@ -785,17 +682,13 @@ def parse_check_false(self, node): for index, value in enumerate(node.args[0].elts): operand_node = self.parse_group(value) assert_node = self.build_assert_false(operand_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].check_stmts.append(assert_node) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) else: operand_node = self.parse_group(node.args[0]) assert_node = self.build_assert_false(operand_node) self.cur_inline_test.check_stmts.append(assert_node) else: - raise MalformedException( - "inline test: invalid check_false(), expected 1 arg" - ) + raise MalformedException("inline test: invalid check_false(), expected 1 arg") def build_assert_neq(self, left_node, comparator_node): equal_node = ast.Compare( @@ -831,17 +724,13 @@ def parse_check_neq(self, node): for index, value in enumerate(node.args[1].elts): comparator_node = self.parse_group(value) assert_node = self.build_assert_neq(left_node, comparator_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].check_stmts.append(assert_node) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) else: comparator_node = self.parse_group(node.args[1]) assert_node = self.build_assert_neq(left_node, comparator_node) self.cur_inline_test.check_stmts.append(assert_node) else: - raise MalformedException( - "inline test: invalid check_neq(), expected 2 args" - ) + raise MalformedException("inline test: invalid check_neq(), expected 2 args") def build_assert_none(self, left_node): equal_node = ast.Compare( @@ -870,17 +759,13 @@ def parse_check_none(self, node): for index, value in enumerate(node.args[0].elts): operand_node = self.parse_group(value) assert_node = self.build_assert_none(operand_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].check_stmts.append(assert_node) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) else: operand_node = self.parse_group(node.args[0]) assert_node = self.build_assert_none(operand_node) self.cur_inline_test.check_stmts.append(assert_node) else: - raise MalformedException( - "inline test: invalid check_none(), expected 1 arg" - ) + raise MalformedException("inline test: invalid check_none(), expected 1 arg") def build_assert_not_none(self, left_node): equal_node = ast.Compare( @@ -909,17 +794,13 @@ def parse_check_not_none(self, node): for index, value in enumerate(node.args[0].elts): operand_node = self.parse_group(value) assert_node = self.build_assert_not_none(operand_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].check_stmts.append(assert_node) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) else: operand_node = self.parse_group(node.args[0]) assert_node = self.build_assert_not_none(operand_node) self.cur_inline_test.check_stmts.append(assert_node) else: - raise MalformedException( - "inline test: invalid check_not_none(), expected 1 arg" - ) + raise MalformedException("inline test: invalid check_not_none(), expected 1 arg") def build_assert_same(self, left_node, comparator_node): equal_node = ast.Compare( @@ -955,17 +836,13 @@ def parse_check_same(self, node): for index, value in enumerate(node.args[1].elts): comparator_node = self.parse_group(value) assert_node = self.build_assert_same(left_node, comparator_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].check_stmts.append(assert_node) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) else: comparator_node = self.parse_group(node.args[1]) assert_node = self.build_assert_same(left_node, comparator_node) self.cur_inline_test.check_stmts.append(assert_node) else: - raise MalformedException( - "inline test: invalid check_same(), expected 2 args" - ) + raise MalformedException("inline test: invalid check_same(), expected 2 args") def build_assert_not_same(self, left_node, comparator_node): equal_node = ast.Compare( @@ -1001,17 +878,13 @@ def parse_check_not_same(self, node): for index, value in enumerate(node.args[1].elts): comparator_node = self.parse_group(value) assert_node = self.build_assert_not_same(left_node, comparator_node) - self.cur_inline_test.parameterized_inline_tests[ - index - ].check_stmts.append(assert_node) + self.cur_inline_test.parameterized_inline_tests[index].check_stmts.append(assert_node) else: comparator_node = self.parse_group(node.args[1]) assert_node = self.build_assert_not_same(left_node, comparator_node) self.cur_inline_test.check_stmts.append(assert_node) else: - raise MalformedException( - "inline test: invalid check_not_same(), expected 2 args" - ) + raise MalformedException("inline test: invalid check_not_same(), expected 2 args") def build_fail(self): equal_node = ast.Compare( @@ -1027,16 +900,10 @@ def parse_fail(self, node): if len(node.args) == 0: self.build_fail() else: - raise MalformedException( - "inline test: fail() does not expect any arguments" - ) + raise MalformedException("inline test: fail() does not expect any arguments") def parse_group(self, node): - if ( - isinstance(node, ast.Call) - and isinstance(node.func, ast.Name) - and node.func.id == self.group_str - ): + if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == self.group_str: # node type is ast.Call, node.func type is ast.Name if sys.version_info >= (3, 8, 0): index_args = [arg.value for arg in node.args] @@ -1044,18 +911,14 @@ def parse_group(self, node): # python3.7 type of arg is ast.Num which does not in higher version index_args = [arg.n for arg in node.args] if self.cur_inline_test.prev_stmt_type != PrevStmtType.CondExpr: - raise MalformedException( - "inline test: Group() must be called to test a conditional statement" - ) + raise MalformedException("inline test: Group() must be called to test a conditional statement") if not self.cur_inline_test.previous_stmts: raise MalformedException("inline test: previous statement not found") stmt = self.cur_inline_test.previous_stmts[0] for i, index_arg in enumerate(index_args): if isinstance(stmt, ast.BoolOp): if index_arg < 0 or index_arg >= len(stmt.values): - raise MalformedException( - f"inline test: Group() {i} index with value {index_arg} out of range" - ) + raise MalformedException(f"inline test: Group() {i} index with value {index_arg} out of range") else: stmt = stmt.values[index_arg] # raise NotImplementedError(index_arg, ast.dump(stmt)) @@ -1064,15 +927,11 @@ def parse_group(self, node): return node def parse_parameterized_test(self): - for index, parameterized_test in enumerate( - self.cur_inline_test.parameterized_inline_tests - ): + for index, parameterized_test in enumerate(self.cur_inline_test.parameterized_inline_tests): parameterized_test.previous_stmts = self.cur_inline_test.previous_stmts parameterized_test.prev_stmt_type = self.cur_inline_test.prev_stmt_type parameterized_test.lineno = self.cur_inline_test.lineno - parameterized_test.test_name = ( - self.cur_inline_test.test_name + "_" + str(index) - ) + parameterized_test.test_name = self.cur_inline_test.test_name + "_" + str(index) def parse_inline_test(self, node): inline_test_calls = [] @@ -1080,16 +939,11 @@ def parse_inline_test(self, node): inline_test_calls.reverse() if len(inline_test_calls) <= 1: - raise MalformedException( - "inline test: invalid inline test, requires at least one assertion" - ) + raise MalformedException("inline test: invalid inline test, requires at least one assertion") # "itest()" or "itest('test name')" or "itest('test name', True)" or "itest(parameterized=True)" or "itest(test_name='test name', parameterized=True)" constructor_call = inline_test_calls[0] - if ( - isinstance(constructor_call.func, ast.Name) - and constructor_call.func.id == self.class_name_str - ): + if isinstance(constructor_call.func, ast.Name) and constructor_call.func.id == self.class_name_str: self.parse_constructor(constructor_call) else: raise MalformedException("inline test: invalid inline test constructor") @@ -1104,10 +958,7 @@ def parse_inline_test(self, node): # "given(a, 1)" for call in inline_test_calls[inline_test_call_index:]: - if ( - isinstance(call.func, ast.Attribute) - and call.func.attr == self.given_str - ): + if isinstance(call.func, ast.Attribute) and call.func.attr == self.given_str: self.parse_given(call) inline_test_call_index += 1 else: @@ -1142,15 +993,11 @@ def parse_inline_test(self, node): f"inline test: given() must be called before check_eq()/check_true()/check_false()" ) else: - raise MalformedException( - f"inline test: invalid function call {self.node_to_source_code(call.func)}" - ) + raise MalformedException(f"inline test: invalid function call {self.node_to_source_code(call.func)}") if self.cur_inline_test.parameterized: self.parse_parameterized_test() - self.inline_test_list.extend( - self.cur_inline_test.parameterized_inline_tests - ) + self.inline_test_list.extend(self.cur_inline_test.parameterized_inline_tests) else: # add current inline test to the list self.inline_test_list.append(self.cur_inline_test) @@ -1261,9 +1108,7 @@ def _find(self, tests, obj, module, globs, seen): valname = "%s" % (valname) # Recurse to functions & classes. - if ( - self._is_routine(val) or inspect.isclass(val) - ) and self._from_module(module, val): + if (self._is_routine(val) or inspect.isclass(val)) and self._from_module(module, val): self._find(tests, val, module, globs, seen) # Look for tests in a class's contained objects. @@ -1274,11 +1119,9 @@ def _find(self, tests, obj, module, globs, seen): val = val.__func__ # Recurse to methods, properties, and nested classes. - if ( - inspect.isroutine(val) - or inspect.isclass(val) - or isinstance(val, property) - ) and self._from_module(module, val): + if (inspect.isroutine(val) or inspect.isclass(val) or isinstance(val, property)) and self._from_module( + module, val + ): valname = "%s" % (valname) self._find(tests, val, module, globs, seen) @@ -1314,8 +1157,32 @@ def __init__( self.runner = runner self.dtest = dtest self.obj = None - self.fixture_request: Optional[FixtureRequest] = None - self.add_marker(pytest.mark.inline) + self._init_fixtureinfo_request() + + # fixture API changed in pytest 8 + # https://github.com/pytest-dev/pytest/issues/11218 + if pytest.version_tuple >= (8, 0, 0): + + def _init_fixtureinfo_request(self) -> None: + self.funcargs: Dict[str, object] = {} + fm = self.session._fixturemanager + fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None) + self._fixtureinfo = fixtureinfo + self.fixturenames = fixtureinfo.names_closure + self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] + + else: + + def _init_fixtureinfo_request(self) -> None: + def func() -> None: + pass + + self.funcargs: Dict[str, object] = {} + fm = self.session._fixturemanager + self._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] + node=self, func=func, cls=None, funcargs=False + ) + self._request = FixtureRequest(self, _ispytest=True) # type: ignore[arg-type] @classmethod def from_parent( @@ -1332,11 +1199,9 @@ def from_parent( def setup(self) -> None: if self.dtest is not None: - self.fixture_request = _setup_fixtures(self) - globs = dict(getfixture=self.fixture_request.getfixturevalue) - for name, value in self.fixture_request.getfixturevalue( - "inlinetest_namespace" - ).items(): + self._request._fillfixtures() + globs = dict(getfixture=self._request.getfixturevalue) + for name, value in self._request.getfixturevalue("inlinetest_namespace").items(): globs[name] = value self.dtest.globs.update(globs) @@ -1374,12 +1239,7 @@ def order_tests(test_list, tags): sorted_ordering[i] = tags.index(tag) # sorting the list based on their tag positions - prio_sorted = [ - val - for (_, val) in sorted( - zip(sorted_ordering, prio_unsorted), key=lambda x: x[0] - ) - ] + prio_sorted = [val for (_, val) in sorted(zip(sorted_ordering, prio_unsorted), key=lambda x: x[0])] prio_sorted.extend(unordered) return prio_sorted @@ -1414,9 +1274,7 @@ def collect(self) -> Iterable[InlinetestItem]: if ordered_list is not None: for test in ordered_list: if ( - test.is_empty() - or (group_tags and len(set(test.tag) & set(group_tags)) == 0) - or test.disabled + test.is_empty() or (group_tags and len(set(test.tag) & set(group_tags)) == 0) or test.disabled ): # skip empty inline tests and tests with tags not in the tag list and disabled tests continue @@ -1428,22 +1286,6 @@ def collect(self) -> Iterable[InlinetestItem]: ) -def _setup_fixtures(inlinetest_item: InlinetestItem) -> FixtureRequest: - """Used by InlinetestItem to setup fixture information.""" - - def func() -> None: - pass - - inlinetest_item.funcargs = {} # type: ignore[attr-defined] - fm = inlinetest_item.session._fixturemanager - inlinetest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] - node=inlinetest_item, func=func, cls=None, funcargs=False - ) - fixture_request = FixtureRequest(inlinetest_item, _ispytest=True) - fixture_request._fillfixtures() - return fixture_request - - ###################################################################################### # Timeout # # Logic # diff --git a/tests/test_plugin.py b/tests/test_plugin.py index a298e13..40c3096 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -2,6 +2,7 @@ from _pytest.pytester import Pytester import pytest + # pytest -p pytester class TestInlinetests: def test_inline_parser(self, pytester: Pytester): @@ -257,9 +258,7 @@ def m(a): """ ) for x in (pytester.path, checkfile): - reprec = pytester.inline_run( - "--inlinetest-group=add", "--inlinetest-group=minus" - ) + reprec = pytester.inline_run("--inlinetest-group=add", "--inlinetest-group=minus") items = [x.item for x in reprec.getcalls("pytest_itemcollected")] assert len(items) == 2 @@ -459,9 +458,7 @@ def m(a): """ ) for x in (pytester.path, checkfile): - reprec = pytester.inline_run( - "--inlinetest-order=minus", "--inlinetest-order=add" - ) + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") items = [x.item for x in reprec.getcalls("pytest_itemcollected")] assert len(items) == 2 assert items[0].dtest.test_name == "2" @@ -481,9 +478,7 @@ def m(a): """ ) for x in (pytester.path, checkfile): - reprec = pytester.inline_run( - "--inlinetest-order=minus", "--inlinetest-order=add" - ) + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") items = [x.item for x in reprec.getcalls("pytest_itemcollected")] assert len(items) == 3 @@ -507,9 +502,7 @@ def m(a): """ ) for x in (pytester.path, checkfile): - reprec = pytester.inline_run( - "--inlinetest-order=minus", "--inlinetest-order=add" - ) + reprec = pytester.inline_run("--inlinetest-order=minus", "--inlinetest-order=add") items = [x.item for x in reprec.getcalls("pytest_itemcollected")] assert len(items) == 4 @@ -700,9 +693,7 @@ def test_mtd(a, c): """ ) for x in (pytester.path, checkfile): - pytester.makefile( - ".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only" - ) + pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-only") items, reprec = pytester.inline_genitems(x) assert len(items) == 1 res = pytester.runpytest() @@ -721,9 +712,7 @@ def test_mtd(): """ ) for x in (pytester.path, checkfile): - pytester.makefile( - ".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable" - ) + pytester.makefile(".ini", pytest="[pytest]\naddopts = -p pytester --inlinetest-disable") items, reprec = pytester.inline_genitems(x) assert len(items) == 1 res = pytester.runpytest() diff --git a/tox.ini b/tox.ini index 3494950..b88fe36 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,20 @@ [tox] isolated_build = True -envlist = - py{37,38,39,310} +requires = tox, tox-gh +env_list = + py37-pytest7 + py{38,39,310}-pytest{7,8} [testenv] -commands = pytest -p pytester deps = - pytest - pytest-cov - flake8 - black -setenv = - PYTHONDEVMODE=1 \ No newline at end of file + pytest7: pytest~=7.0 + pytest8: pytest~=8.0 +commands = + pytest {posargs} + +[gh] +python = + 3.7 = py37-pytest7 + 3.8 = py38-pytest7, py38-pytest8 + 3.9 = py39-pytest7, py39-pytest8 + 3.10 = py310-pytest7, py310-pytest8