Skip to content

Commit

Permalink
Merge pull request #6889 from smokestacklightnin/ci/testing/pytest-up…
Browse files Browse the repository at this point in the history
…-and-running

Revamp testing workflow and use `pytest` instead of `unittest`
  • Loading branch information
peytondmurray authored Sep 3, 2024
2 parents dcc7f49 + 29adb58 commit ae2767a
Show file tree
Hide file tree
Showing 360 changed files with 1,888 additions and 2,827 deletions.
99 changes: 36 additions & 63 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
@@ -1,89 +1,62 @@
# Github action definitions for ci-test with PRs.
# Github action definitions for unit-tests with PRs.

name: tfx-ci-test
name: tfx-unit-tests
on:
pull_request:
branches: [ master ]
paths-ignore:
- '**.md'
- 'docs/**'
workflow_dispatch:

env:
USE_BAZEL_VERSION: "6.5.0"
# Changed to match tensorflow
# https://github.com/tensorflow/tensorflow/blob/master/.bazelversion

jobs:
build:
tests:
if: github.actor != 'copybara-service[bot]'
runs-on: ubuntu-latest
timeout-minutes: 60

strategy:
matrix:
python-version: ['3.9', '3.10']
which-tests: ["not e2e", "e2e"]
dependency-selector: ["NIGHTLY", "DEFAULT"]

steps:
- uses: actions/checkout@v2
- name: Get Changed Files
id: changed_files
uses: trilom/file-changes-action@v1.2.4
with:
fileOutput: ' '
- name: Select files to check
run: |
# Filter out non-python files.
(cat $HOME/files_added.txt; echo; cat $HOME/files_modified.txt) | tr ' ' '\n' | grep '\.py$' > py_files.txt || true
# Filter out non-test python files and e2e or integration tests.
cat py_files.txt | grep '_test\.py$' | grep -v _e2e_ | grep -v integration | grep -v 'examples/' > py_test_files.txt || true
# Select proto files.
(cat $HOME/files_added.txt; echo; cat $HOME/files_modified.txt) | tr ' ' '\n' | grep '\.proto$' > proto_files.txt || true
- uses: actions/checkout@v4

- name: Set up Python 3.9
uses: actions/setup-python@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: 3.9

- name: Set up Bazel 5.3.0
run: |
# Instruction from https://docs.bazel.build/versions/master/install-ubuntu.html
curl -sSL https://github.com/bazelbuild/bazel/releases/download/5.3.0/bazel-5.3.0-installer-linux-x86_64.sh -o bazel_installer.sh
chmod +x bazel_installer.sh
sudo ./bazel_installer.sh
- name: Cache pip
uses: actions/cache@v2
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: |
setup.py
tfx/dependencies.py
- name: Set up Bazel
uses: bazel-contrib/setup-bazel@0.8.5
with:
# This path is specific to Ubuntu
path: ~/.cache/pip
# Look to see if there is a cache hit for the corresponding setup.py + TFX version
key: ${{ runner.os }}-pip-${{ hashFiles('tfx/dependencies.py') }}-
restore-keys: |
${{ runner.os }}-pip-
# Avoid downloading Bazel every time.
bazelisk-cache: true
# Store build cache per workflow.
disk-cache: ${{ github.workflow }}-${{ hashFiles('.github/workflows/ci-test.yml') }}
# Share repository cache between workflows.
repository-cache: true

- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel
# TODO(b/232490018): Cython need to be installed separately to build pycocotools.
python -m pip install Cython -c ./test_constraints.txt
TFX_DEPENDENCY_SELECTOR=NIGHTLY pip install -c ./test_constraints.txt --extra-index-url https://pypi-nightly.tensorflow.org/simple --pre .[all]
- name: Run unit tests
shell: bash
run: |
[ ! -s "py_test_files.txt" ] || cat py_test_files.txt | xargs -I {} python {}
- name: Lint with protolint
continue-on-error: true
pip install -c ./test_constraints.txt --extra-index-url https://pypi-nightly.tensorflow.org/simple --pre --editable .[all]
env:
PROTOLINT_VERSION: 0.25.1
shell: bash
run: |
curl -sSOL https://github.com/yoheimuta/protolint/releases/download/v${PROTOLINT_VERSION}/protolint_${PROTOLINT_VERSION}_Linux_x86_64.tar.gz
tar zxf protolint_${PROTOLINT_VERSION}_Linux_x86_64.tar.gz
echo "[NOTE] This linter is currently EXPERIMENTAL.======================================="
echo "Please contact reviewers for existing lint errors or false negative errors."
echo "===================================================================================="
[ ! -s "proto_files.txt" ] || cat proto_files.txt | xargs -I {} ./protolint {}
TFX_DEPENDENCY_SELECTOR: ${{ matrix.dependency-selector }}

- name: Lint with pylint
continue-on-error: true
- name: Run unit tests
shell: bash
run: |
pip install pylint
echo "[NOTE] This linter is currently EXPERIMENTAL.======================================="
echo "Please contact reviewers for existing lint errors or false negative errors."
echo "Feel free to send PRs for pylintrc in the root directory of the repository if needed."
echo "===================================================================================="
[ ! -s "py_files.txt" ] || pylint $(cat py_files.txt | tr '\n' ' ')
pytest -m "${{ matrix.which-tests }}"
55 changes: 51 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,18 +144,65 @@ which is a subclass of
We have several types of tests in this repo:

* Unit tests for source code;
* End to end tests (filename ends with `_e2e_test.py`): some of this also runs
with external environments.
* End to end tests (filenames end with `_e2e_test.py`): some of these also run
with external environments;
* Integration tests (filenames end with `_integration_test.py`): some of these might
run with external environments;
* Performance tests (filenames end with `_perf_test.py`): some of these might
run with external environments.

### Running Unit Tests

At this point all unit tests are safe to run externally. We are working on
porting the end to end tests.

Each test can just be invoked with `python`. To invoke all unit tests:
To run all tests:

```shell
find ./tfx -name '*_test.py' | grep -v e2e | xargs -I {} python {}
pytest
```

Each test can be run individually with `pytest`:

```shell
pytest tfx/a_module/a_particular_test.py
```

Some tests are slow and are given the `pytest.mark.slow` mark. These tests
are slow and/or require more dependencies.

```shell
pytest -m "slow"
```

To invoke all unit tests not marked as slow:

```shell
pytest -m "not slow"
```

To invoke end to end tests:

```shell
pytest -m "e2e"
```

To skip end to end tests:

```shell
pytest -m "not e2e"
```

To invoke integration tests:

```shell
pytest -m "integration"
```

To invoke performance tests:

```shell
pytest -m "perf"
```

## Running pylint
Expand Down
10 changes: 10 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[pytest]
addopts = --import-mode=importlib
testpaths = tfx
python_files = *_test.py
norecursedirs = custom_components .* *.egg
markers =
e2e: end to end tests that are slow and require more dependencies (deselect with '-m "not e2e"')
integration: integration tests that are slow and require more dependencies (deselect with '-m "not integration"')
perf: performance "perf" tests that are slow and require more dependencies (deselect with '-m "not perf"')
serial
4 changes: 0 additions & 4 deletions tfx/components/bulk_inferrer/component_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,3 @@ def testConstructOutputExample(self):
'Examples', bulk_inferrer.outputs[
standard_component_specs.OUTPUT_EXAMPLES_KEY].type_name)
self.assertNotIn('inference_result', bulk_inferrer.outputs.keys())


if __name__ == '__main__':
tf.test.main()
4 changes: 0 additions & 4 deletions tfx/components/bulk_inferrer/executor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,3 @@ def testDoWithOutputExamplesSpecifiedSplits(self):
self.assertFalse(
fileio.exists(
os.path.join(self._output_examples_dir, 'Split-unlabelled2')))


if __name__ == '__main__':
tf.test.main()
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,3 @@ def test_convert_for_predict_invalid_output_example_spec(self, input_key):
""", bulk_inferrer_pb2.OutputExampleSpec())
with self.assertRaises(ValueError):
utils.convert(prediction_log, output_example_spec)


if __name__ == '__main__':
tf.test.main()
4 changes: 0 additions & 4 deletions tfx/components/distribution_validator/component_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,3 @@ def testConstruct(self):
restored_config = distribution_validator.exec_properties[
standard_component_specs.DISTRIBUTION_VALIDATOR_CONFIG_KEY]
self.assertEqual(config, restored_config)


if __name__ == '__main__':
tf.test.main()
11 changes: 6 additions & 5 deletions tfx/components/distribution_validator/executor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
# limitations under the License.
"""Tests for tfx.distribution_validator.executor."""


import pytest
import os
import tempfile

from absl import flags
from absl.testing import absltest
from absl.testing import parameterized
from tensorflow_data_validation.anomalies.proto import custom_validation_config_pb2
from tfx.components.distribution_validator import executor
Expand Down Expand Up @@ -551,6 +552,8 @@ def testMissBaselineStats(self):
},
)

@pytest.mark.xfail(run=False, reason="PR 6889 This test fails and needs to be fixed. "
"If this test passes, please remove this mark.", strict=True)
def testStructData(self):
source_data_dir = FLAGS.test_tmpdir
stats_artifact = standard_artifacts.ExampleStatistics()
Expand Down Expand Up @@ -1011,6 +1014,8 @@ def testStructData(self):
}
"""
})
@pytest.mark.xfail(run=False, reason="PR 6889 This test fails and needs to be fixed. "
"If this test passes, please remove this mark.", strict=True)
def testEmptyData(self, stats_train, stats_eval, expected_anomalies):
source_data_dir = FLAGS.test_tmpdir
stats_artifact = standard_artifacts.ExampleStatistics()
Expand Down Expand Up @@ -1410,7 +1415,3 @@ def testInvalidArtifactDVConfigAndParameterConfig(self):
_ = distribution_validator_executor.Do(
input_dict, output_dict, exec_properties
)


if __name__ == '__main__':
absltest.main()
8 changes: 4 additions & 4 deletions tfx/components/distribution_validator/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# limitations under the License.
"""Tests for tfx.components.distribution_validator.utils."""


import pytest
import os

from absl import flags
Expand All @@ -29,6 +31,8 @@

class UtilsTest(tf.test.TestCase):

@pytest.mark.xfail(run=False, reason="PR 6889 This test fails and needs to be fixed. "
"If this test passes, please remove this mark.", strict=True)
def test_load_config_from_artifact(self):
expected_config = text_format.Parse(
"""default_slice_config: {
Expand Down Expand Up @@ -57,7 +61,3 @@ def test_load_config_from_artifact(self):

read_binary_config = utils.load_config_from_artifact(config_artifact)
self.assertProtoEquals(read_binary_config, expected_config)


if __name__ == '__main__':
tf.test.main()
4 changes: 0 additions & 4 deletions tfx/components/evaluator/component_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,3 @@ def testConstructDuplicateUserModule(self):
example_splits=['eval'],
module_file='module_file_path',
module_path='python.path.module')


if __name__ == '__main__':
tf.test.main()
14 changes: 8 additions & 6 deletions tfx/components/evaluator/executor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# limitations under the License.
"""Tests for tfx.components.evaluator.executor."""


import pytest
import glob
import os

Expand All @@ -31,6 +33,7 @@
from tfx.utils import proto_utils



class ExecutorTest(tf.test.TestCase, parameterized.TestCase):

@parameterized.named_parameters(
Expand Down Expand Up @@ -80,6 +83,8 @@ class ExecutorTest(tf.test.TestCase, parameterized.TestCase):
]))
}, True),
)
@pytest.mark.xfail(run=False, reason="PR 6889 This test fails and needs to be fixed. "
"If this test passes, please remove this mark.")
def testEvalution(self, exec_properties, model_agnostic=False):
source_data_dir = os.path.join(
os.path.dirname(os.path.dirname(__file__)), 'testdata')
Expand Down Expand Up @@ -178,7 +183,7 @@ def testDoLegacySingleEvalSavedModelWFairness(self, exec_properties):
# post-export metric is registered. This may raise an ImportError if the
# currently-installed version of TFMA does not support fairness
# indicators.
import tensorflow_model_analysis.addons.fairness.post_export_metrics.fairness_indicators # pylint: disable=g-import-not-at-top, unused-import
import tensorflow_model_analysis.addons.fairness.post_export_metrics.fairness_indicators # noqa: F401
exec_properties[
standard_component_specs
.FAIRNESS_INDICATOR_THRESHOLDS_KEY] = '[0.1, 0.3, 0.5, 0.7, 0.9]'
Expand Down Expand Up @@ -295,6 +300,8 @@ def testDoLegacySingleEvalSavedModelWFairness(self, exec_properties):
},
True,
False))
@pytest.mark.xfail(run=False, reason="PR 6889 This test fails and needs to be fixed. "
"If this test passes, please remove this mark.")
def testDoValidation(self, exec_properties, blessed, has_baseline):
source_data_dir = os.path.join(
os.path.dirname(os.path.dirname(__file__)), 'testdata')
Expand Down Expand Up @@ -353,8 +360,3 @@ def testDoValidation(self, exec_properties, blessed, has_baseline):
else:
self.assertTrue(
fileio.exists(os.path.join(blessing_output.uri, 'NOT_BLESSED')))


if __name__ == '__main__':
tf.compat.v1.enable_v2_behavior()
tf.test.main()
4 changes: 0 additions & 4 deletions tfx/components/example_diff/component_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,3 @@ def testConstruct(self):
restored_config = example_diff.exec_properties[
standard_component_specs.EXAMPLE_DIFF_CONFIG_KEY]
self.assertEqual(restored_config, config)


if __name__ == '__main__':
tf.test.main()
5 changes: 0 additions & 5 deletions tfx/components/example_diff/executor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import os
import tempfile

from absl.testing import absltest
from absl.testing import parameterized
import tensorflow_data_validation as tfdv
from tensorflow_data_validation.skew import feature_skew_detector
Expand Down Expand Up @@ -205,7 +204,3 @@ def testDo(self,
for output in all_outputs:
split_pair = output.split('SplitPair-')[1]
self.assertIn(split_pair, expected_split_pair_names)


if __name__ == '__main__':
absltest.main()
4 changes: 0 additions & 4 deletions tfx/components/example_gen/base_example_gen_executor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,3 @@ def testInvalidFeatureBasedPartitionWithProtos(self):
RuntimeError, 'Split by `partition_feature_name` is only supported '
'for FORMAT_TF_EXAMPLE and FORMAT_TF_SEQUENCE_EXAMPLE payload format.'):
example_gen.Do({}, self._output_dict, self._exec_properties)


if __name__ == '__main__':
tf.test.main()
Loading

0 comments on commit ae2767a

Please sign in to comment.