question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Add option to ignore differences in line endings during string comparison, so tests are not OS dependent

See original GitHub issue

When writing tests that verify multi-line strings, for example file contents, it’s common to use Kotest string assertions. However these tests are highly sensitive to line endings.

This assertion will pass on Unix, and fail on Windows:

https://github.com/kotest/kotest/blob/a7195a4d1c4a0d9e20526bc336338d394e028003/kotest-framework/kotest-framework-multiplatform-plugin-gradle/src/test/kotlin/io/kotest/framework/multiplatform/gradle/KotestMultiplatformCompilerGradlePluginSpec.kt#L38-L43

This PR also fixed line-ending tests failing https://github.com/kotest/kotest/pull/2743

I have some thoughts on how to implement this.

Additional arg on matchers

One option is to add an arg, like ignoreLineEndings, to all string comparison matchers which defaults to true, but this would mean they could no longer be infix funs. Adding an overload with a ignoreLineEndings arg could work around that, but that’s a lot of work, code, maintenance, and testing.

Example: new overloads are verbose and repetitive

// existing function
infix fun <A : CharSequence?> A.shouldStartWith(prefix: CharSequence): A {
   this should startWith(prefix)
   return this
}

// new overload
fun <A : CharSequence?> A.shouldStartWith(prefix: CharSequence, ignoreLineEndings: Boolean = false): A {
   this shouldNot startWith(prefix, ignoreLineEndings)
   return this
}

// existing function
infix fun <A : CharSequence?> A.shouldNotStartWith(prefix: CharSequence): A {
   this shouldNot startWith(prefix)
   return this
}

// new overload
fun <A : CharSequence?> A.shouldNotStartWith(prefix: CharSequence, ignoreLineEndings: Boolean = false): A {
   this shouldNot startWith(prefix, ignoreLineEndings)
   return this
}

Boolean vs Sealed class flag

Even if there is an additional arg, there are drawbacks to using a boolean flag, as it is restricted in allowing for different options. Instead a sealed class/enum parameter would allow for more customisation, for example controlling exactly how the line endings are handled. Perhaps someone wants line endings in the ‘expected’ string to be converted from /r/n to /n, but on the ‘actual’ string left alone. Or vice versa, or both.

Project config option

A sealed class flag could be added to all string comparators, but it would be very verbose and repetitively. I think that all strings in a project would be compared the same way, there’s no need for a per-assertion config, and a flag only needs to be set once.

One propose adding a project configuration option to control how line endings are dealt with.

object KotestProjectConfig : AbstractProjectConfig() {
    override val stringLineEndingsMode = StringLineEndings.MapToUnix
}

This flag can then be used in all string comparison matchers (but I’m not sure how this can be done… do the matchers have access to the project config?)

Helper functions

Possibly the easiest option is just providing some helper functions, although their use in tests could be pretty repetitive and verbose

// note: this is just a quick example, the regex isn't correct
private val windows = Regex("\r\n")
private val unix = Regex("\n")

fun String.lineEndingsToUnix() = replace(windows, "\n")
fun String.lineEndingsToWindows() = replace(unix, "\r\n")

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:10 (8 by maintainers)

github_iconTop GitHub Comments

2reactions
sksamuelcommented, Aug 29, 2022

I like the last suggestion too as we could have multiple options like trim and case insensitive

1reaction
Kantiscommented, Aug 29, 2022

I like the last suggestion too as we could have multiple options like trim and case insensitive

I like it too. Perhaps the pattern could also be re-used for other things that also takes optional configuration, such as:

fun test() {
   """{"id": 1, "age": 20}""" shouldEqualJson {
      fieldComparison = FieldComparison.Lenient
      propertyOrder = PropertyOrder.Strict
      
      """{"age": 20}"""
   }

  timestamp shouldBeCloseTo {
     window = 2.seconds
     now()
  }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Add isEqualToNormalizingNewlines assertion · Issue #818
Add option to ignore differences in line endings during string comparison, so tests are not OS dependent kotest/kotest#3161.
Read more >
Compare strings while ignoring line endings - Stack Overflow
Using method split to compare string word-by-word: >>> " x y\nz".split() # return list of "words" separated by whitespace characters ["x", ...
Read more >
How to compare strings - C# Guide - Microsoft Learn
Learn how to compare and order string values, with or without case, ... Linguistic comparisons are culture and platform-dependent.
Read more >
AssertJ - fluent assertions java library - GitHub Pages
AssertJ can print each assertion description (when it is set), to do so call Assertions ... It does not assert anything assertThat(actual.equals(expected));.
Read more >
How to capture warnings — pytest documentation
Although not recommended, you can use the --disable-warnings command-line option to suppress the warning summary entirely from the test run output. Disabling ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found