Development¶
So you’ve decided to tinker with Supertag. Great! Let’s get you set up. First you’ll want to make sure your development environment is setup. See Development Install.
Running tests¶
Running the test suite is easy:
scripts/run_tests.sh
If you’re on Linux, this runs about ~110 tests and finishes pretty quickly. If you’re on MacOS, I believe it is around
~140 tests, and is much slower. This is because we have to run with --test-threads=1
due to some weird threading
quirks.
If a test breaks, and you want to run it individually, run the following:
STAG_LOG=1 scripts/itest.sh test_funky_name
This runs the test_funky_name
test. STAG_LOG=1
will enable full output logging at the trace level and higher.
This is necessary for debugging the really tricky bugs.
Note
STAG_LOG=1
also tee’s the output to ./itest.log
, so you don’t need to less/grep the test output yourself,
just grep that logging file.
Writing tests¶
The best advice for writing Supertag tests is to look at the existing tests to see how they work. They’re readable and mostly straightforward (as much as they can be).
Supporting all usages¶
As outlined in Usage, there are 3 different ways of accomplishing most operations. Writing a good test means covering these 3 methods. We don’t want new functionality that only works for the file browser GUI, but not the commandline, for example.
To make sure our bases are covered, we use the following pattern: for any given test, you will want to write 3 different versions of it, all using the same underlying logic. Here is an example of a real Supertag test that tests that when you remove a tag directory, everything works as expected:
#[test]
fn test_rm_tagdir_cli() -> TestResult {
let th = TestHelper::new(None);
_test_rm_tagdir(th)
}
#[test]
fn test_rm_tagdir_manual() -> TestResult {
let mut th = TestHelper::new(None);
th.symlink_mode = OpMode::MANUAL;
th.rm_mode = OpMode::MANUAL;
th.rmdir_mode = OpMode::MANUAL;
_test_rm_tagdir(th)
}
#[test]
#[cfg(target_os = "macos")]
fn test_rm_tagdir_finder() -> TestResult {
let mut th = TestHelper::new(None);
th.symlink_mode = OpMode::FINDER;
th.rm_mode = OpMode::FINDER;
th.rmdir_mode = OpMode::FINDER;
_test_rm_tagdir(th)
}
/// Tests removing a leaf tag removes the tag from all of the files in the path intersection
fn _test_rm_tagdir(th: TestHelper) -> TestResult {
// ...
Ok(())
}
Notice the naming convention used here. We have one function (the last function) that captures the “meat” of the test,
and it is named _TEST_NAME
. Then we have 3 ancillary functions (the first 3) and they are named:
TEST_NAME_cli
TEST_NAME_manual
TEST_NAME_finder
The job of an ancillary test is to instantiate the TestHelper
struct and then set it up to behave as either a
the tag binary (OpMode::CLI
), a manual system program (OpMode::MANUAL
), or as the MacOS Finder gui
(OpMode::FINDER
). Then the ancillary test should call the function that has the meat of the test.
We set up these behaviors by setting different mode attributes on the TestHelper
.
Here is a (currently) complete list of the different modes we can set:
- symlink_mode
Affects how linking a file to a tag is performed.
- rmdir_mode
Affects how removing a tag or tag group is performed.
- rm_mode
Affects how removing a file in Supertag is performed.
- mkdir_mode
Affects how creating a tag is performed.
- rename_mode
Affects how moving a tag or a file is performed.
- import_mode
Affects how a file is imported into Supertag.
Each of these modes attempts to behave as if the user performed the action from the tag binary, manually with a
standard OS binary, or from MacOS Finder. The default behavior for all modes is OpMode::CLI
, which corresponds to
the tag binary. Different OpMode
may result in drastically different behavior, for example, when using
symlink_mode
, the following operations are performed:
OpMode::CLI
: file is linked to tag by calling thesupertag::ln
function.OpMode::MANUAL
: file is linked to the tag by calling theln
system binary.OpMode::FINDER
: file is linked to the tag by creating and writing an alias file.
In summary, try to write tests that cover the functionality you’re testing from the different ways it can be used.
Debugging tests¶
There exists a super helpful method on the TestHelper
struct: inspect()
. Anywhere in your test, if you call it,
execution will stop and you will receive some debugging information related to your temporary collection’s mountpoint:
db: /tmp/pd-m6lRH4/config/collections/col-IqO2UD/col-IqO2UD.db
mounted at: /tmp/col-IqO2UD
project dir: /tmp/pd-m6lRH4
The test will then wait for a newline character on STDIN. This allows you to inspect the various aspects of the
temporary filesystem to debug what could be going wrong. If you’re on Linux, inspect will also open sqlitebrowser
and a file browser.
When things go wrong¶
Things can and will go wrong when writing your tests. You can end up in different states that are not good for your system, for example: many temporary Supertag filesystems mounted.
Cleaning up filesystems¶
The following script will do its best to kill off zombie Supertag filesystems:
scripts/cleanup_test_mounts.sh
It is not perfect, however. For example, as it is based on a specific filesystem naming scheme, in this case
supertag:itest_col
, it will not clean up zombie Supertag filesystems from non-test runs. For those, you will need
to run a force umount directly:
sudo umount -f the_supertag_filesystem_name
Contributing¶
CLA¶
If you’d like to make a contribution back upstream to Supertag, please be aware that we require a Contributors License Agreement (CLA). This is because we may offer a dual-licensed version of Supertag in the future.