-*- text -*- TREE CONFLICT TESTING STRATEGY This document describes how we are testing the code that detects, reports and resolves tree conflicts. We'd like to make the testing, and the tree conflicts feature itself, more transparent and open to contributions. For tree conflicts, there already exist cmdline tests for the update, switch, merge, commit, status, info and revert commands. We've added tree_conflicts_tests.py, not to replace the other tests, but rather to complement them by offering a generic way to create lots of tree-conflict detection test scenarios. The generic framework is not yet finished, but we think it will be useful as we extend the tree-conflict feature beyond its original use cases. ================ The Declarations ================ The new tree-conflict testing framework offers a compact, declarative format for test definitions. Elementary actions are combined into scenarios, scenarios are bundled into sets, and the sets are fed into a generic tree-conflict-maker. A scenario can be committed to the test repository and then applied to the working copy by an update, switch or merge operation. In another test, the same scenario can modify the working copy prior to an update or switch operation. An advantage of this abstraction is that it allows us to create additional tests easily through code reuse. It also helps us to see beyond our 6 original use cases. A disadvantage is that test failures are rather opaque, but that could probably be fixed with some Python wizardry. The changes that can cause tree conflicts are composed from a set of elementary actions, each named according to its function. For instance, fD signifies running 'svn delete' on a file. The first character of an action name specifies the type of the item acted upon. The names of the items are fixed. f_ Item is the file 'F' d_ Item is the directory 'D' The second character of an action name can specify an svn operation. _P - Change a Property. (Note: Presently just sets a particular property value, even if that property already had that value.) _A - Create the item by Adding an unversioned item (and set a property on it as well). (Suggestion: Don't set a property here; let the user do so explicitly with '_P' when desired. We'd have to ensure that a subsequent '_P' later in the same test would still cause a change; presently it would not.) _C - Create the item by Copying from an existing one. _M - Move the item to a different name. _D - Delete the item. Alternately, the second character can specify a non-svn filesystem operation. _t Append text to the file. _a Create the item on disk. _d Delete the item from disk. To help detect bugs in the test scenarios, each action, except for _a, first asserts that the item (F or D) exists on disk. Some actions operate on 2 items. _C copies F1 to F (or D1 to D) and _M moves F to F2 (or D to D2). The items F, F1, D and D1 are created, added and committed by a generic test-setup function. F2 and D2 do not exist at the start of a test. The arguments for copy and move are not symmetrical because we are interested only in the destination of a copy and the source of a move. The source of a copy is uninteresting, and the destination of a move is the same as that of a copy. The elementary actions are combined to form "scenarios". A scenario is a literal Python tuple containing two lists of actions: the first list creates the starting point for the change to be tested, and the second being the actual change to be tested. For example, this scenario represents a simple deletion of a file: ( ['fa','fA'], ['fD'] ) ================= Behind the Scenes ================= How are the scenarios actually used? The generic tree-conflict-maker is ensure_tree_conflict(). This function applies two sets of scenarios. The "incoming" set is applied to the repository, and the "localmod" set is applied to the working copy. Each possible combination of incoming and localmod scenarios is tested as an independent subtest. The incoming scenarios are prepared as follows. 1. Run the usual Subversion test setup, sbox.build(), which creates a test repository containing the "greek tree" (as revision 1) and checks out a working copy of it. 2. For each incoming scenario, create the scenario path via 'svn mkdir'. 3. For each incoming scenario, execute its initialisation actions which will typically create the file or directory that will be acted on later. 4. Commit as revision 2. 5. For each incoming scenario, execute its actions on the F or D in its scenario path. 6. Commit as revision 3. Now the repository is loaded with all of the incoming scenarios. To run the actual subtests, each incoming scenario must be applied to each localmod scenario. 1. Check out a fresh working copy at revision 2. 2. Execute the localmod scenario's actions on the F or D in its scenario path. 3. For each incoming scenario, run the given svn command (e.g. update) on the incoming scenario's path, then run 'svn status' on the same path. If the path is tree-conflicted, we're happy. The working copy is deleted and the steps are repeated for next localmod scenario. If any failure occurs, the whole test is marked as a failure in the test output. Each test scenario is executed in a unique path created from the actions in the action list, concatenated with "_" between them. For the above example, that path would be simply "fD". The use of a unique path could allow running many of them in parallel. Currently, we run the scenarios one-by-one, each in a fresh working copy. ============== Current Status ============== The following features are sketched out in the scenario data, but not tested: Obstructions Replacement (file->file, dir->dir) 'svn switch'