Unit tests
Unit tests#
Unit tests for showyourwork! are located in the tests
subdirectory of the
showyourwork
package. Each unit test subclasses the TemporaryShowyourworkRepository
class in tests/helpers/temp_repo.py
, which defines an asynchronous method
called test_repo
to test the workflow on a given repository by running a
series of steps. Each step is defined by a method within the class, such as
create_remote
to create the remote repository on GitHub, create_local
to create and initialize the repository locally, customize
to add/edit
files in the repository, build_local
to run the
workflow locally, and run_github_action
to push the changes to the remote
and trigger the showyourwork-action
run. There are a few other methods
as well – see tests/helpers/temp_repo.py
for details.
To create a new unit test, subclass TemporaryShowyourworkRepository
and
override any of the aforementioned methods. As an example, consider the unit
test that checks the ability of showyourwork
to dynamically generate
text in a TeX file:
from helpers import TemporaryShowyourworkRepository
from showyourwork.config import edit_yaml
# A script that computes the age of the universe
variable_script = r"""
import paths
import numpy as np
# Compute the age of the universe
np.random.seed(42)
age = np.random.normal(14.0, 1.0)
# Write it to disk
with open(paths.output / "age_of_universe.txt", "w") as f:
f.write(f"{age:.3f}")
"""
class TestLatexVariable(TemporaryShowyourworkRepository):
"""Test a workflow with dynamic quantities imported into the tex file."""
def customize(self):
"""Create and edit all the necessary files for the workflow."""
# Create the script
with open(
self.cwd / "src" / "scripts" / "age_of_universe.py", "w"
) as f:
print(variable_script, file=f)
# Import the variable into the tex file
ms = self.cwd / "src" / "tex" / "ms.tex"
with open(ms, "r") as f:
ms_orig = f.read()
with open(ms, "w") as f:
ms_new = ms_orig.replace(
r"\end{document}",
r"Based on a detailed analysis of Planck observations of the cosmic "
r"microwave background, we have determined the age of the universe "
r"to be \variable{output/age_of_universe.txt} Gyr."
"\n"
r"\end{document}",
)
print(ms_new, file=f)
# Add a Snakemake rule to run the script
with open(self.cwd / "Snakefile", "r") as f:
contents = f.read()
with open(self.cwd / "Snakefile", "w") as f:
print(contents, file=f)
print("\n", file=f)
print(
"\n".join(
[
"rule age_of_universe:",
" output:",
" 'src/tex/output/age_of_universe.txt'",
" script:",
" 'src/scripts/age_of_universe.py'",
]
),
file=f,
)
This test subclasses TemporaryShowyourworkRepository
and overrides a single
method: customize
, which makes local changes to the repository before
attempting to run the workflow. In the code above, we create a Python script
(src/scripts/age_of_universe.py
), which outputs the age of the universe
(purportedly from Planck cosmic microwave background data)
to the file src/tex/output/age_of_universe.txt
.
We then edit the default ms.tex
file (which is generated during the create_local
step of the unit test) to include a call to \variable{output/age_of_universe.txt}
,
which imports the value generated by the script into the manuscript.
Finally, we add a Snakemake rule telling the workflow how to generate
the output from the Python script.
That’s it! The parent class (TemporaryShowyourworkRepository
) takes care
of the rest.
Note that by default, unit tests create a temporary repository in the
github.com/showyourwork
organization with the same name as the unit
test (but in snake_case
instead of camelCase
). If the remote build
is successful, this repository gets automatically deleted. If it fails, the
repository hangs around so you can inspect the build logs. Future runs of the
test will delete existing repositories with the same name and re-create them.
All of this, of course, relies on users having write privileges to
github.com/showyourwork
. If you do not have such privileges but would still
like to create and experiment with unit tests for showyourwork
, you can either
run the tests in debug mode by setting the DEBUG=true
environment variable
prior to calling pytest
(e.g., DEBUG=true pytest
) or setting the
class variable local_build_only = True
at the top of your class.
Warning
For users with write privileges: the current approach will
NOT scale well as the number of unit tests N
or the frequency of commits
increase, since upon every commit we create and delete N
GitHub
repositories within the organization. There are no hard limits on this
number (since the repos are all public), but as N
gets large this
could make folks at GitHub unhappy. At that point, we should reconsider
this approach.