Getting Started

What is ornithology?

Ornithology is a Python-based testing framework for HTCondor. It uses pytest as its test execution engine, and provides a wide variety of tools for inspecting the state of an HTCondor pool.

Ornithology is designed to be flexible and extensible. If you need a new tool that might be useful in other contexts, feel free to add such a tool to the ornithology` module itself.

An annotated version of a test file written using Ornithology, test_hold_and_release.py, is shown below. The goal of the test is to walk through the process of submitting a job, letting it start running, then holding it, then releasing it, then allowing it to finish. Ornithology is used to submit the job, react to events, and confirm that the expected order of events occurred.

#!/usr/bin/env pytest

from ornithology import (
    config,
    standup,
    action,
    SetAttribute,
    SetJobStatus,
    JobStatus,
    in_order,
)

# This is a pytest "fixture" - a re-usable blob of code to run.
# Its return value can be made available to tests by putting the name of the
# fixture in the test function arguments.
@action
def job_queue_events_for_sleep_job(default_condor, path_to_sleep):
    # Submit a single job that sleeps for 10 seconds.
    # "path_to_sleep" is a fixture provided by Ornithology itself
    # (the path to a cross-platform sleep script).
    handle = default_condor.submit(
        description={"executable": path_to_sleep, "arguments": "10"},
        count=1,
    )

    # The id (cluster.proc) of the first (and only) job in the submit.
    jobid = handle.job_ids[0]

    # Wait for (and react to) events in the job queue log.
    default_condor.job_queue.wait_for_events(
        {  # this dictionary maps job ids to (event, reaction) tuples, or just to events to wait for, in order
            jobid: [
                (  # when the job starts running, hold it
                    SetJobStatus(JobStatus.RUNNING),
                    lambda jobid, event: default_condor.run_command(
                        ["condor_hold", jobid]
                    ),
                ),
                (  # once the job is held, release it
                    SetJobStatus(JobStatus.HELD),
                    lambda jobid, event: default_condor.run_command(
                        ["condor_release", jobid]
                    ),
                ),
                # finally, wait for the job to complete
                SetJobStatus(JobStatus.COMPLETED),
            ]
        },
        # All waits must have a timeout.
        timeout=120,
    )

    # This returns all of the job events in the job queue log for the jobid,
    # making that information available to downstream fixtures/tests.
    return default_condor.job_queue.by_jobid[jobid]


# All tests must be methods of classes; the name of the class must be Test*
class TestCanHoldAndReleaseJob:
    # Methods that begin with test_* are tests.

    # The "self" argument is required by Python, but unnecessary for Ornithology.
    # The "job_queue_events_for_sleep_job" brings in the return value of the fixture defined above.
    def test_job_queue_events_in_correct_order(self, job_queue_events_for_sleep_job):
        # "in_order" is an assertion helper provided by Ornithology.
        # It tests whether the items in the first argument appear in the order given
        # by the second argument (allowing extra items between the expected ones).
        assert in_order(
            job_queue_events_for_sleep_job,
            [
                SetJobStatus(JobStatus.IDLE),
                SetJobStatus(JobStatus.RUNNING),
                SetJobStatus(JobStatus.HELD),
                SetJobStatus(JobStatus.IDLE),
                SetJobStatus(JobStatus.RUNNING),
                SetJobStatus(JobStatus.COMPLETED),
            ],
        )

    # Another test, to check that the hold reason code was what we expected.
    def test_hold_reason_code_was_1(self, job_queue_events_for_sleep_job):
        assert SetAttribute("HoldReasonCode", "1") in job_queue_events_for_sleep_job

We recommend reading through test_curl_plugin.py to get a feel for how to build an Ornithology test from the ground up.

Running Ornithology Tests

To run the Ornithology tests, you must be using Python 3 and have the Python 3 HTCondor/ClassAd libraries in your PYTHONPATH. Assuming you set CMAKE_INSTALL_PREFIX to ~/install, after make install finishes, you’ll set PYTHONPATH to ~/install/lib/python3. Make sure that your installed bin directory is in your PATH (before any system version of HTCondor), and that CONDOR_CONFIG points to HTCondor under test. [1]

You’ll need pytest and one extension to run the Ornithology tests. The easiest way to handle this is with Pip, although you can use your system pytest (and pytest-httpserver), or Miniconda, if you desire:

$ cd condor_tests
$ pip3 install --user -r requirements.txt

This may install a pytest executable early enough in your PATH to be useful, but assuming it doesn’t, you can start a specific Ornithology test in the following way:

$ python3 -m pytest test_run_sleep_job.py

One of the lines early in the output will look like the following:

Base per-test directory: /tmp/condor-tests-1598380839-15666

which will not be cleaned up after the test runs, for your debugging convenience.

Ornithology tests can also be run under ctest. In that case, the test directory will end up under src/condor_tests/${TEST_NAME}_ctest/tests-dirs. Note that ctest will use your system Python, so you must install pytest to your system Python.

Running Ornithology Tests in the BaTLab

Ornithology tests can run as part of the standard BaTLab/Metronome test process.

Which tests are run is controlled by src/condor_tests/CMakeLists.txt.

The line which runs the test_hold_and_release.py test above is

condor_pl_test(test_hold_and_release "Submit a job, hold it, release it, run it completion" "core;quick;full" CTEST DEPENDS "src/condor_tests/ornithology;src/condor_tests/conftest.py")

… which looks like any other test declaration.

When running on the BaTLab, Ornithology test directories are available in the test results tarball under condor_tests/test-dirs.