{
  "part": 0,
  "title": "Environment setup",
  "reset": "none",
  "steps": [
    { "id": "0.1", "title": "Deploy code by svn update and verify (TESTING_automation.md 9.3, not TESTING.md's file-copy)",
      "_comment": "Self-teardown (TESTING_automation.md 9.4): destroys every numbered scratch dataset this catalogue ever creates so a retry can never collide with a prior run's leftover snapshot names. All missing_ok - a no-op against an already-clean environment. {SRC}/{DST} themselves are left in place for Step 0.3's already-idempotent zfs_create -p. {TRANSPORT}/{REPORT}/{ONESHOT} are plain folders, not datasets, under the folder-based-transport design - destroy_dataset's allow-list doesn't apply to them at all.",
      "actions": [
        { "do": "zfs_destroy", "dataset": "{SRC}/ds1", "missing_ok": true },
        { "do": "zfs_destroy", "dataset": "{SRC}/ds2", "missing_ok": true },
        { "do": "zfs_destroy", "dataset": "{SRC}/ds3", "missing_ok": true },
        { "do": "zfs_destroy", "dataset": "{SRC}/ds4", "missing_ok": true },
        { "do": "zfs_destroy", "dataset": "{DST}/ds1", "missing_ok": true },
        { "do": "zfs_destroy", "dataset": "{DST}/ds2", "missing_ok": true },
        { "do": "zfs_destroy", "dataset": "{DST}/ds3", "missing_ok": true },
        { "do": "zfs_destroy", "dataset": "{DST}/ds4", "missing_ok": true },
        { "do": "zfs_destroy", "dataset": "{VERIFY}", "missing_ok": true },
        { "do": "rm_matching", "dir": "{SN}", "pattern": "^sneakernet_target\\.status(\\.[0-9_.-]+)?$" },
        { "do": "rm_matching", "dir": "{SN}", "pattern": "^sneakernet\\.conf\\.yaml\\.bak\\.\\S+$" },
        { "do": "svn_update", "dir": "{CODE}",
          "capture": { "name": "SVN_UPDATE_OUT" } },
        { "do": "svn_status", "dir": "{CODE}",
          "capture": { "name": "SVN_STATUS_LINES", "reduce": "join" } },
        { "do": "run_perl_c", "path": "{SN}/sneakernet",
          "capture": { "name": "SN_PERLC" } },
        { "do": "run_perl_c", "path": "{CODE}/ZFS_Utils.pm",
          "capture": { "name": "ZU_PERLC" } }
      ],
      "assertions": [
        { "type": "regex_absent", "var": "SVN_STATUS_LINES", "pattern": "^[MC]", "message": "svn status shows no locally-modified (M) or conflicted (C) files - only untracked (?) runtime artifacts are expected" },
        { "type": "regex_present", "var": "SN_PERLC", "pattern": "syntax OK", "message": "sneakernet passes perl -c" },
        { "type": "regex_present", "var": "ZU_PERLC", "pattern": "syntax OK", "message": "ZFS_Utils.pm passes perl -c" }
      ]
    },
    { "id": "0.2", "title": "Confirm the version floor",
      "actions": [
        { "do": "read_file", "path": "{CODE}/ZFS_Utils.pm",
          "capture": { "name": "ZU_VER", "select": "our \\$VERSION = '([\\d.]+)'", "expect_count": 1 } },
        { "do": "read_file", "path": "{SN}/sneakernet",
          "capture": { "name": "SN_VER", "select": "our \\$VERSION = '([\\d.]+)'", "expect_count": 1 } }
      ],
      "assertions": [
        { "type": "true", "var": "ZU_VER", "message": "ZFS_Utils.pm has a \\$VERSION" },
        { "type": "true", "var": "SN_VER", "message": "sneakernet has a \\$VERSION" }
      ]
    },
    { "id": "0.3", "title": "Create the test dataset tree",
      "actions": [
        { "do": "zfs_create", "dataset": "{SRC}" },
        { "do": "zfs_create", "dataset": "{SRC}/ds1" },
        { "do": "zfs_create", "dataset": "{SRC}/ds2" },
        { "do": "zfs_create", "dataset": "{DST}" }
      ]
    },
    { "id": "0.4", "title": "Generate a test transport encryption key",
      "actions": [
        { "do": "random_hex", "bytes": 32, "store_as": "TESTKEY" },
        { "do": "write_file", "path": "{HARNESS}/testkey.txt", "content": "$TESTKEY" }
      ],
      "assertions": [
        { "type": "regex_present", "var": "TESTKEY", "pattern": "^[0-9a-f]{64}$", "message": "test key is 64 lowercase hex characters" }
      ]
    },
    { "id": "0.5", "title": "Install the canonical config, generated from TESTING.md Step 0.5's literal YAML (TESTING_automation.md 9.1) - NOT a copy of sneakernet.conf.yaml.original",
      "_comment": "The harness writes this file directly (write_file), rather than expressing it as mutations against a pre-existing file - there is no pre-existing file yet at this point in a clean run.",
      "actions": [
        { "do": "write_file", "path": "{CONFIG}", "content": "---\ndatasets:\n  ds1:\n    dataset: ds1\n    source: storage/testing/src\n    target: storage/testing/dst\n    #maxDelta: 0.9\n  ds2:\n    dataset: ds2\n    source: storage/testing/src\n    target: storage/testing/dst\n    #maxDelta: 0.9\ndebug: '0'\ndisplayLogsOnConsole: '1'\ndisplayLogsOnTTY: ''\ndryrun: '0'\nverbosity: '4'\nlogFile: /storage/testing/code/sneakernet/sneakernet.log\nstatusFile: /storage/testing/code/sneakernet/sneakernet_target.status\nstatusFileBackups: 5\nsource:\n  hostname: test-source\n  poolname: storage/testing/src\n  runningAverageCount: 6\n  historyFile: /storage/testing/code/sneakernet/history.tsv\n  cleanUpScriptsDir: /storage/testing/code/sneakernet/cleanupScripts\n  oneShotCleanup: /storage/testing/oneshot\n  targetSnapshotList: ''\n  fullSendPolicy: warn\n  cleanupScriptSchedule:\n    helloWorld:\n    - 1\n    - 2\n    - 3\n    - 4\n    - 5\n    - 6\n    - 7\n    - 8\n    - 9\n    - 10\n    - 11\n    - 12\n  report:\n    email: ''\n    subject: ''\n    targetDrive:\n      label: ''\n      mountPoint: ''\ntarget:\n  hostname: test-target\n  poolname: storage/testing/dst\n  stateFileName: target_state.txt\n  stateFile: /storage/testing/code/sneakernet/states/targetState\n  cleanUpScriptsDir: cleanUpScripts\n  allowFullOverwrite: 0\n  shutdownAfterReplication: 0\n  report:\n    email: ''\n    subject: ''\n    targetDrive:\n      label: ''\n      mountPoint: /storage/testing/report\ntransport:\n  label: ''\n  mountPoint: /storage/testing/transport\n  pathSubstitution: '.'\n  datasetDir: datasets\n  serialFile: serial.txt\n  encryptionKey: $TESTKEY\n  verifyStream: header\n  compression:\n    method: 'off'\n    level: 6\n    threads: 0\n    nice: 0\n" }
      ],
      "assertions": [
        { "type": "file_nonempty", "path": "{CONFIG}", "message": "config file was written" }
      ]
    },
    { "id": "0.6", "title": "Smoke test", "depends_on": ["0.3", "0.4", "0.5"],
      "actions": [
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds1@smoke1" },
        { "do": "zfs_snapshot", "dataset": "{SRC}/ds2@smoke1" },
        { "do": "run_sneakernet", "servername": "test-source", "verbosity": 2, "log": "part0_source", "expect_exit": 0 },
        { "do": "run_sneakernet", "servername": "test-target", "verbosity": 2, "log": "part0_target", "expect_exit": 0 },
        { "do": "zfs_list_all", "dataset": "{DST}", "capture": { "name": "DST_ALL", "reduce": "join" } }
      ],
      "assertions": [
        { "type": "regex_present", "var": "DST_ALL", "pattern": "dst/ds1\\@smoke1", "message": "ds1's snapshot replicated to the target" },
        { "type": "regex_present", "var": "DST_ALL", "pattern": "dst/ds2\\@smoke1", "message": "ds2's snapshot replicated to the target" }
      ]
    }
  ]
}
