Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
philderbeast committed Dec 23, 2024
1 parent 80552bc commit f5f8037
Showing 1 changed file with 138 additions and 9 deletions.
147 changes: 138 additions & 9 deletions changelog.d/pr-10646.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ platform's path separator.

## Changes in `cabal-testsuite`

### Read Multiline Strings Verbatim

With `ghc-9.12.1` adding `-XMultilineStrings` it would be easier to write
multiline strings in `cabal-testsuite/PackageTests/**/*.test.hs` scripts but the
catch is we run these tests with older `GHC` versions so would need to use
Expand All @@ -59,13 +61,140 @@ The other process accessing the file is `C:\WINDOWS\System32\svchost.exe`
running a `QueryDirectory` event and this problem only occurs when the test
fails.

The `assertOutputContains` function was modifying the output but in a way not
"visible" enough. An added `assertOn` function (that `assertOutputContains`
calls) takes a `NeedleHaystack` configuration for how the search is made, what
to expect and and how to display the expected and actual values. A pilcrow ¶ is
often used to visibly display line endings but our terminal output is restricted
to ASCII so lines are visibly delimited between `^` and `$` visible markers. The
needle (the expected output fragment) is shown annotated this way and the
haystack (the output) can optionally be shown this way too.
### Existing Actual Value Changes

The `assertOutputContains` function was modifying the actual value (the test
output) with `concatOutput` before checking if it contained the expected value.
This function, now renamed as `lineBreaksToSpaces`, would remove CR values and
convert LF values to spaces.

```haskell
-- | Replace line breaks with spaces, correctly handling @"\\r\\n"@.
--
-- >>> lineBreaksToSpaces "foo\nbar\r\nbaz"
-- "foo bar baz"
--
-- >>> lineBreaksToSpaces "foo\nbar\r\nbaz\n"
-- "foo bar baz"
--
-- >>> lineBreaksToSpaces "\nfoo\nbar\r\nbaz\n"
-- " foo bar baz"
lineBreaksToSpaces :: String -> String
```

With this set up, false positives were possible:

* An expected value set up using string gaps without LF characters would match
an output value of "foo_bar_baz", where '_' was any of space, LF or CRLF:

```
let expect = "foo \
\bar \
\baz"
```

* An expected value set up using string gaps with LF characters or with
`-XMultilineStrings` would match an output value of "foo_bar_baz", where '_'
was either of LF or CRLF:

```
let expect = "foo\n\
\bar\n\
\baz"
```

```
{-# LANGUAGE MultilineStrings #-}

let expect = """
foo
bar
baz
"""
```

There are two problems with this:

1. The actual value was changed before comparison and this change was not visible.
2. The expected value was not changed in the same way.

### Explicit Changes and Visible Line Delimiters

To fix these problems, an added `assertOn` function takes a `NeedleHaystack` configuration for how the search is made, what
to expect and and how to display the expected and actual values.

A pilcrow is often used to visibly display line endings but our terminal
output is restricted to ASCII so lines are visibly delimited between `^` and `$`
visible markers. The needle (the expected output fragment) is shown annotated
this way and the haystack (the output) can optionally be shown this way too.

We can now implement `assertOutputContains` by calling `assertOn`:

```diff
assertOutputContains :: MonadIO m => WithCallStack (String -> Result -> m ())
- assertOutputContains needle result =
- withFrozenCallStack $
- unless (needle `isInfixOf` (concatOutput output)) $
- assertFailure $ " expected: " ++ needle
- where output = resultOutput result
+ assertOutputContains = assertOn
+ needleHaystack
+ {txHaystack =
+ TxContains
+ { txBwd = delimitLines
+ , txFwd = encodeLf
+ }
+ }
```

This is still lenient but `encodeLf` doesn't replace LF with spaces like
`concatOutput` (`lineBreaksToSpaces`) did:

```haskell
-- | Replace line CRLF line breaks with LF line breaks.
--
-- >>> encodeLf "foo\nbar\r\nbaz"
-- "foo\nbar\nbaz"
--
-- >>> encodeLf "foo\nbar\r\nbaz\n"
-- "foo\nbar\nbaz\n"
--
-- >>> encodeLf "\nfoo\nbar\r\nbaz\n"
-- "\nfoo\nbar\nbaz\n"
--
-- >>> encodeLf "\n\n\n"
-- "\n\n\n"
encodeLf :: String -> String
```

If you choose to display the actual value by setting
`NeedleHaystack{displayHaystack = True}` then its lines will be delimited.

```haskell
-- | Mark lines with visible delimiters, @^@ at the start and @$@ at the end.
--
-- >>> delimitLines ""
-- "^$"
--
-- >>> delimitLines "\n"
-- "^$\n"
--
-- >>> delimitLines "\n\n"
-- "^$\n^$\n"
--
-- >>> delimitLines "\n\n\n"
-- "^$\n^$\n^$\n"
--
-- >>> delimitLines $ encodeLf "foo\nbar\r\nbaz"
-- "^foo$\n^bar$\n^baz$"
--
-- >>> delimitLines $ encodeLf "foo\nbar\r\nbaz\n"
-- "^foo$\n^bar$\n^baz$\n"
--
-- >>> delimitLines $ encodeLf "\nfoo\nbar\r\nbaz\n"
-- "^$\n^foo$\n^bar$\n^baz$\n"
delimitLines:: String -> String
```

The `concatOutput` function is renamed to `lineBreaksToSpaces`.
If you want a lot of control over the matching then using `assertOn` will allow
applying the same kinds of transformations to the needle value too.

0 comments on commit f5f8037

Please sign in to comment.