|
1 | | -name: CI Tests |
| 1 | +# CI.yml |
| 2 | +# This file contains the script used by GitHub actions to execute the Continuous Integration (CI) |
| 3 | +# for RMG-Py. This includes building RMG and its dependencies, executing the unit tests, |
| 4 | +# functional tests, database tests, and regression tests. |
| 5 | +# |
| 6 | +# This will run automatically on any push to any branch, but will only run one instance of |
| 7 | +# itself at a time per branch (to avoid spawning tons of runners, which prevents them from |
| 8 | +# executing). |
| 9 | +# |
| 10 | +# In the regression testing section of the action the term "Stable" or "Reference" refers to |
| 11 | +# the 'correct answers' to the regression tests, i.e. the way that the main branch executes |
| 12 | +# them. These 'answers' are re-generated daily, or on any push to main, and retrieved whenever |
| 13 | +# a push is made to a non-main branch. The new proposed changes are referred to as "Dynamic". |
| 14 | +# |
| 15 | +# Changelog: |
| 16 | +# 2023-04 - Jackson Burns - Added this header, regression tests, cleanup of action in |
| 17 | +# in general, and documentation throughout the file. |
| 18 | +# 2023-05 - added Docker build steps |
| 19 | +# 2023-05-12 - added changes to allow running on forks |
| 20 | +# 2023-06-06 - added matrix build for libstdcxx-ng versions 12 and 13 on ubuntu. Only expect 12 to work. |
| 21 | +# 2023-06-07 - updated regression testing. Now fails if significant changes are detected. |
| 22 | +# 2023-06-15 - revert changes from 06-06, both now work |
| 23 | +name: Continuous Integration |
2 | 24 |
|
3 | 25 | on: |
| 26 | + schedule: |
| 27 | + # * is a special character in YAML so you have to quote this string |
| 28 | + - cron: "0 8 * * *" |
| 29 | + # runs on all branches on both RMG-Py and forks |
4 | 30 | push: |
| 31 | + # runs on PRs against RMG-Py (and anywhere else, but we add this for RMG-Py) |
5 | 32 | pull_request: |
6 | | - branches: |
7 | | - - main |
8 | | - types: [opened, synchronize, reopened, ready_for_review, review_requested] |
| 33 | + # allow calling from other repos in the ReactionMechanismGenerator organization |
| 34 | + workflow_call: |
| 35 | + |
| 36 | +# this prevents one PR from simultaneously running multiple runners, which will clog up the queue |
| 37 | +# and prevent other PRs from running the CI |
| 38 | +concurrency: |
| 39 | + group: ${{ github.workflow }}-${{ github.ref }} |
| 40 | + cancel-in-progress: true |
| 41 | + |
9 | 42 | jobs: |
10 | | - build-and-test-linux: |
11 | | - runs-on: ubuntu-latest |
| 43 | + build-and-test-unix: |
12 | 44 | strategy: |
13 | | - max-parallel: 5 |
14 | | - env: # update this if needed to match a pull request on the RMG-database |
| 45 | + matrix: |
| 46 | + os: [ubuntu-latest, macos-latest] |
| 47 | + runs-on: ${{ matrix.os }} |
| 48 | + continue-on-error: ${{ matrix.os == 'macos-latest' }} |
| 49 | + # skip scheduled runs from forks |
| 50 | + if: ${{ !( github.repository != 'ReactionMechanismGenerator/RMG-Py' && github.event_name == 'schedule' ) }} |
| 51 | + env: |
| 52 | + # Update this if needed to match a pull request on the RMG-database: |
15 | 53 | RMG_DATABASE_BRANCH: update_atom_enthalpies |
| 54 | + # This is true only if this is a reference case for the regression testing: |
| 55 | + REFERENCE_JOB: ${{ github.ref == 'refs/heads/main' && matrix.os =='ubuntu-latest' && github.repository == 'ReactionMechanismGenerator/RMG-Py' }} |
16 | 56 | defaults: |
17 | 57 | run: |
18 | 58 | shell: bash -l {0} |
19 | 59 | steps: |
20 | | - - uses: actions/checkout@v2 |
21 | | - - uses: conda-incubator/setup-miniconda@v2 |
| 60 | + - uses: actions/checkout@v3 |
| 61 | + |
| 62 | + # configures the mamba environment manager and builds the environment |
| 63 | + - name: Setup Mambaforge Python 3.7 |
| 64 | + uses: conda-incubator/setup-miniconda@v2 |
22 | 65 | with: |
23 | 66 | environment-file: environment.yml |
| 67 | + miniforge-variant: Mambaforge |
| 68 | + miniforge-version: latest |
24 | 69 | python-version: 3.7 |
25 | 70 | activate-environment: rmg_env |
26 | | - - name: Install codecov |
27 | | - run: conda install -y -c conda-forge codecov |
28 | | - - name: Conda info |
29 | | - run: | |
30 | | - conda info |
31 | | - conda list |
32 | | - - name: Install and link Julia dependencies |
33 | | - run: | |
34 | | - julia -e "using Pkg; Pkg.add(PackageSpec(url=\"https://github.com/ReactionMechanismGenerator/ReactionMechanismSimulator.jl\", rev=\"main\"))" |
35 | | - julia -e "using Pkg; Pkg.add(\"PyCall\"); Pkg.add(\"DifferentialEquations\")" |
36 | | - python -c "import julia; julia.install()" |
37 | | - ln -sfn $(which python-jl) $(which python) |
38 | | - - name: Install MOPAC |
39 | | - env: |
40 | | - MOPACKEY: ${{ secrets.MOPACKEY }} |
41 | | - timeout-minutes: 1 |
42 | | - continue-on-error: true # allowed to fail on pull request from a forked repository |
| 71 | + use-mamba: true |
| 72 | + |
| 73 | + # list the environment for debugging purposes |
| 74 | + - name: mamba info |
43 | 75 | run: | |
44 | | - set +o pipefail |
45 | | - yes 'Yes' | ${CONDA_PREFIX}/bin/mopac "$MOPACKEY" |
46 | | - - name: Install and compile RMG |
| 76 | + mamba info |
| 77 | + mamba list |
| 78 | +
|
| 79 | + # Clone the other needed repository |
| 80 | + - name: Clone RMG-Database |
47 | 81 | run: | |
48 | 82 | cd .. |
49 | 83 | git clone -b $RMG_DATABASE_BRANCH https://github.com/ReactionMechanismGenerator/RMG-database.git |
50 | | - cd RMG-Py |
51 | | - git clone -b arkanepy3 https://github.com/mjohnson541/Q2DTor.git external/Q2DTor |
| 84 | +
|
| 85 | + # modify env variables as directed in the RMG installation instructions |
| 86 | + - name: Set Environment Variables |
| 87 | + run: | |
| 88 | + RUNNER_CWD=$(pwd) |
| 89 | + echo "PYTHONPATH=$RUNNER_CWD/RMG-Py:$PYTHONPATH" >> $GITHUB_ENV |
| 90 | + echo "$RUNNER_CWD/RMG-Py" >> $GITHUB_PATH |
| 91 | +
|
| 92 | + # RMG build step |
| 93 | + - name: make RMG |
| 94 | + run: | |
| 95 | + make clean |
52 | 96 | make |
53 | | - - name: Trigger RMG-tests |
54 | | - if: ${{ github.event_name == 'push' && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/stable' }} # only push events to branches other than main and stable |
55 | | - env: |
56 | | - GH_TOKEN: ${{ secrets.RMG_DEV_TOKEN }} |
57 | | - run: ./trigger-rmg-tests.sh |
| 97 | +
|
| 98 | + # RMS installation and linking to Julia |
| 99 | + # Allow these installs to 'fail' (as they do in RMG-Tests) with the command || True trick |
| 100 | + - name: Install and link Julia dependencies |
| 101 | + timeout-minutes: 120 # this usually takes 20-45 minutes (or hangs for 6+ hours). |
| 102 | + run: | |
| 103 | + python -c "import julia; julia.install(); import diffeqpy; diffeqpy.install()" || true |
| 104 | + julia -e 'using Pkg; Pkg.add(PackageSpec(name="ReactionMechanismSimulator",rev="main")); using ReactionMechanismSimulator' || true |
| 105 | +
|
| 106 | + # non-regression testing |
58 | 107 | - name: Unit tests |
59 | 108 | run: make test-unittests |
60 | 109 | - name: Functional tests |
61 | | - if: ${{ success() || failure() }} # Run even if the unit tests failed (but not if they were cancelled) |
62 | 110 | run: make test-functional |
63 | 111 | - name: Database tests |
64 | | - if: ${{ success() || failure() }} # Run even if the functional tests failed (but not if they were cancelled) |
65 | 112 | run: make test-database |
66 | | - - name: Code coverage |
67 | | - run: codecov |
| 113 | + |
| 114 | + # Regression Testing - Test Execution |
| 115 | + - name: Regression Tests - Execution |
| 116 | + id: regression-execution |
| 117 | + timeout-minutes: 60 |
| 118 | + run: | |
| 119 | + for regr_test in aromatics liquid_oxidation nitrogen oxidation sulfur superminimal RMS_constantVIdealGasReactor_superminimal RMS_CSTR_liquid_oxidation RMS_liquidSurface_ch4o2cat; |
| 120 | + do |
| 121 | + if python-jl rmg.py test/regression/"$regr_test"/input.py; then |
| 122 | + echo "$regr_test" "Executed Successfully" |
| 123 | + else |
| 124 | + echo "$regr_test" "Failed to Execute" | tee -a $GITHUB_STEP_SUMMARY |
| 125 | + export FAILED=Yes |
| 126 | + fi |
| 127 | + done |
| 128 | + if [[ ${FAILED} ]]; then |
| 129 | + echo "One or more regression tests could not be executed." | tee -a $GITHUB_STEP_SUMMARY |
| 130 | + echo "Please download the failed results or check the above log to see why." | tee -a $GITHUB_STEP_SUMMARY |
| 131 | + exit 1 |
| 132 | + fi |
| 133 | +
|
| 134 | + # Upload Regression Results as Failed if above step failed |
| 135 | + - name: Upload Failed Results |
| 136 | + if: ${{ failure() && steps.regression-execution.conclusion == 'failure' }} |
| 137 | + uses: actions/upload-artifact@v3 |
| 138 | + with: |
| 139 | + name: failed regression results ${{ matrix.os }} |
| 140 | + path: | |
| 141 | + test/regression |
| 142 | +
|
| 143 | + # Upload Regression Results as Stable if Scheduled or Push to Main |
| 144 | + - name: Upload Results as Reference |
| 145 | + # upload the results for scheduled CI (on main) and pushes to main |
| 146 | + if: ${{ env.REFERENCE_JOB == 'true' }} |
| 147 | + uses: actions/upload-artifact@v3 |
| 148 | + with: |
| 149 | + name: stable_regression_results |
| 150 | + path: | |
| 151 | + test/regression |
| 152 | +
|
| 153 | + # Upload Regression Results as Dynamic if Push to non-main Branch |
| 154 | + - name: Upload Results as Dynamic |
| 155 | + if: ${{ env.REFERENCE_JOB == 'false' }} |
| 156 | + uses: actions/upload-artifact@v3 |
| 157 | + with: |
| 158 | + name: dynamic regression results ${{ matrix.os }} |
| 159 | + path: | |
| 160 | + test/regression |
| 161 | +
|
| 162 | + - name: mkdir stable_regression_results |
| 163 | + if: ${{ env.REFERENCE_JOB == 'false' }} |
| 164 | + run: mkdir stable_regression_results |
| 165 | + |
| 166 | + # Retrieve Stable Results for reference |
| 167 | + # Will need to use this -> https://github.com/dawidd6/action-download-artifact |
| 168 | + - name: Retrieve Stable Regression Results |
| 169 | + if: ${{ env.REFERENCE_JOB == 'false' }} |
| 170 | + uses: dsnopek/action-download-artifact@91dda23aa09c68860977dd0ed11d93c0ed3795e7 # see https://github.com/ReactionMechanismGenerator/RMG-Py/pull/2459#issuecomment-1582850815 |
| 171 | + with: |
| 172 | + # this will search for the last successful execution of CI on main and download |
| 173 | + # the stable regression results |
| 174 | + workflow: CI.yml |
| 175 | + workflow_conclusion: success |
| 176 | + repo: ReactionMechanismGenerator/RMG-Py |
| 177 | + branch: main |
| 178 | + name: stable_regression_results |
| 179 | + path: stable_regression_results |
| 180 | + search_artifacts: true # retrieves the last run result, either scheduled daily or on push to main |
| 181 | + ensure_latest: true # ensures that the latest run is retrieved |
| 182 | + # should result in a set of folders inside stable_regression_results |
| 183 | + # each of which has the stable result for that example/test |
| 184 | + |
| 185 | + # Regression Testing - Actual Comparisons |
| 186 | + - name: Regression Tests - Compare to Baseline |
| 187 | + id: regression-comparison |
| 188 | + if: ${{ env.REFERENCE_JOB == 'false' }} |
| 189 | + env: |
| 190 | + REFERENCE: stable_regression_results |
| 191 | + run: | |
| 192 | + mkdir -p "test/regression-diff" |
| 193 | + for regr_test in aromatics liquid_oxidation nitrogen oxidation sulfur superminimal RMS_constantVIdealGasReactor_superminimal RMS_CSTR_liquid_oxidation; |
| 194 | + do |
| 195 | + echo "" |
| 196 | + echo "## Regression test $regr_test:" |
| 197 | + # Memory Usage and Execution Time |
| 198 | + echo -n 'Reference: ' |
| 199 | + grep "Execution time" $REFERENCE/"$regr_test"/RMG.log | tail -1 |
| 200 | + echo -n 'Current: ' |
| 201 | + grep "Execution time" test/regression/"$regr_test"/RMG.log | tail -1 |
| 202 | + echo -n 'Reference: ' |
| 203 | + grep "Memory used:" $REFERENCE/"$regr_test"/RMG.log | tail -1 |
| 204 | + echo -n 'Current: ' |
| 205 | + grep "Memory used:" test/regression/"$regr_test"/RMG.log | tail -1 |
| 206 | +
|
| 207 | + # Compare the edge and core |
| 208 | + if python-jl scripts/checkModels.py \ |
| 209 | + "$regr_test-core" \ |
| 210 | + $REFERENCE/"$regr_test"/chemkin/chem_annotated.inp \ |
| 211 | + $REFERENCE/"$regr_test"/chemkin/species_dictionary.txt \ |
| 212 | + test/regression/"$regr_test"/chemkin/chem_annotated.inp \ |
| 213 | + test/regression/"$regr_test"/chemkin/species_dictionary.txt |
| 214 | + then |
| 215 | + echo "$regr_test Passed Core Comparison" |
| 216 | + else |
| 217 | + echo "$regr_test Failed Core Comparison" | tee -a $GITHUB_STEP_SUMMARY |
| 218 | + cp "$regr_test-core.log" test/regression-diff/ |
| 219 | + export FAILED=Yes |
| 220 | + fi |
| 221 | + if python-jl scripts/checkModels.py \ |
| 222 | + "$regr_test-edge" \ |
| 223 | + $REFERENCE/"$regr_test"/chemkin/chem_edge_annotated.inp \ |
| 224 | + $REFERENCE/"$regr_test"/chemkin/species_edge_dictionary.txt \ |
| 225 | + test/regression/"$regr_test"/chemkin/chem_edge_annotated.inp \ |
| 226 | + test/regression/"$regr_test"/chemkin/species_edge_dictionary.txt |
| 227 | + then |
| 228 | + echo "$regr_test Passed Edge Comparison" |
| 229 | + else |
| 230 | + echo "$regr_test Failed Edge Comparison" | tee -a $GITHUB_STEP_SUMMARY |
| 231 | + cp "$regr_test-edge.log" test/regression-diff/ |
| 232 | + export FAILED=Yes |
| 233 | + fi |
| 234 | +
|
| 235 | + # Check for Regression between Reference and Dynamic (skip superminimal) |
| 236 | + if [ -f test/regression/"$regr_test"/regression_input.py ]; |
| 237 | + then |
| 238 | + if python-jl rmgpy/tools/regression.py \ |
| 239 | + test/regression/"$regr_test"/regression_input.py \ |
| 240 | + $REFERENCE/"$regr_test"/chemkin \ |
| 241 | + test/regression/"$regr_test"/chemkin |
| 242 | + then |
| 243 | + echo "$regr_test Passed Observable Testing" |
| 244 | + else |
| 245 | + echo "$regr_test Failed Observable Testing" | tee -a $GITHUB_STEP_SUMMARY |
| 246 | + export FAILED=Yes |
| 247 | + fi |
| 248 | + fi |
| 249 | + echo "" |
| 250 | + done |
| 251 | + if [[ ${FAILED} ]]; then |
| 252 | + echo "One or more regression tests failed." | tee -a $GITHUB_STEP_SUMMARY |
| 253 | + echo "Please download the failed results and run the tests locally or check the above log to see why." | tee -a $GITHUB_STEP_SUMMARY |
| 254 | + exit 1 |
| 255 | + fi |
| 256 | + # Upload Regression Results as Failed if above step failed |
| 257 | + - name: Upload Failed Comparison Results |
| 258 | + if: ${{ failure() && steps.regression-comparison.conclusion == 'failure' }} |
| 259 | + uses: actions/upload-artifact@v3 |
| 260 | + with: |
| 261 | + name: failed regression comparison results ${{ matrix.os }} |
| 262 | + path: | |
| 263 | + test/regression-diff |
| 264 | + # Install and Call codecov only if the tests were successful (permitting failures in the regression comparison tests) |
| 265 | + - name: Code coverage install and run |
| 266 | + if: success() || ( failure() && steps.regression-execution.conclusion == 'success' ) |
| 267 | + run: | |
| 268 | + mamba install -y -c conda-forge codecov |
| 269 | + codecov |
| 270 | +
|
| 271 | + build-and-push-docker: |
| 272 | + # after testing and on pushes to main, build and push docker image |
| 273 | + # technically we could live without the 'needs' since _in theory_ |
| 274 | + # nothing will ever be merged into main that fails the tests, but |
| 275 | + # who knows ¯\_(ツ)_/¯ |
| 276 | + # |
| 277 | + # taken from https://github.com/docker/build-push-action |
| 278 | + needs: build-and-test-unix |
| 279 | + runs-on: ubuntu-latest |
| 280 | + if: github.ref == 'refs/heads/main' && github.repository == 'ReactionMechanismGenerator/RMG-Py' |
| 281 | + steps: |
| 282 | + - name: Set up QEMU |
| 283 | + uses: docker/setup-qemu-action@v2 |
| 284 | + |
| 285 | + - name: Set up Docker Buildx |
| 286 | + uses: docker/setup-buildx-action@v2 |
| 287 | + |
| 288 | + - name: Login to Docker Hub |
| 289 | + uses: docker/login-action@v2 |
| 290 | + with: |
| 291 | + username: ${{ secrets.DOCKERHUB_USERNAME }} |
| 292 | + password: ${{ secrets.DOCKERHUB_TOKEN }} |
| 293 | + |
| 294 | + - name: Build and Push |
| 295 | + uses: docker/build-push-action@v4 |
| 296 | + with: |
| 297 | + push: true |
| 298 | + tags: reactionmechanismgenerator/rmg:latest |
0 commit comments