600f80d0

Fail on empty scenario directories, and scenarios that are directories

An empty `scenarioDir` suite now fails, and scenarios can be directories.

### Fail on an empty scenario directory

An empty scenario directory defined no tests at all, so a suite whose scenario files went missing, left out of `extra-source-files` or behind a mistyped path, reported success. `scenarioDir` and `scenarioDirRecur` now define a single failing test when they find no files. An absent directory takes the same path, because `forgivingAbsence` already turns absence into an empty list.

This is the reasoning that already makes an empty test suite fail, applied one level down.

### `scenarioDirOfDirs`

A scenario often consists of more than one file: an input and the output it must produce, a config and a golden rendering of it. The two existing definers can only hand a single file to the test definition, so such a suite had to reconstruct the grouping by hand.

```haskell
scenarioDirOfDirs "test_resources/same" $ \fp ->
  it "contains two files with the same contents" $ do
    a <- readFile (fp </> "a")
    b <- readFile (fp </> "b")
    a `shouldBe` b
```

Each subdirectory is one scenario, whatever it contains. Files in the given directory itself are ignored, and so is any nesting below the subdirectories. There is no recursive variant, because a scenario directory has no obvious depth at which to stop.

The three definers now share a helper parameterised over how it lists scenarios, so the empty case behaves identically for all of them.

### A separable third commit

`Test.Syd.TimingSpec.long diff timing is bounded` failed in two of three `nix flake check` runs here, unrelated to the above; the first failure predates the `scenarioDirOfDirs` work. The test bounds its inner run at 120s, deliberately generously, so that it fails only if the 2s diff timeout stopped working. The default test timeout is 60s, so on a machine slow enough for that bound to matter the suite killed the test first: `Timeout!` after ~65s in the nix sandbox, while printing the two 100k-element lists. The last commit raises the timeout for that one test above the bound it asserts. Drop that commit if you would rather handle it separately.

### Testing

`test_resources/same/{numbers,words}/{a,b}` exercises the real path. Beyond that, `ScenarioSpec` builds specs with `execTestDefM`, runs them, and asserts over `computeTestSuiteStats`: the empty and absent directories produce exactly one failure and no successes, and a directory holding one subdirectory, a nested subdirectory inside it, and a loose file produces exactly one scenario. The empty-directory tests fail against the unmodified library, and the nesting test fails if the lister is swapped for `listDirRecurRel`.

`stack test sydtest-test --pedantic` passes, and `nix flake check` passes on a run that rebuilt most of the tree in parallel, which is the load that previously exposed the timing flake.

### Breaking

`scenarioDir` and `scenarioDirRecur` now fail where they previously defined nothing, so sydtest is bumped to 0.28.0.0.

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 0s 2s 2s 2s 0s
Eval 2s 15s 15s 18s 0s
Build 17s 1m10s 1m06s 1m23s 0s
Suite 0s 1m29s 1m23s 1m23s 0s

Timeline

0s10s20s30s40s50s1m1m10s1m20s