blatyo

blatyo

Conduit Core Team

How to get better diff comparison for strings with newlines?

I’m testing some complex ecto sql builder stuff. So I pass data to generate an ecto query and I want to validate that it produces the right query. So, I’m using the Repo.to_sql function to generate SQL and then asserting that matches the expected SQL.

That works OK, but when the SQL is long, it makes it hard to understand which parts of the query differ. I wrote something that can format SQL, because I thought it’d improve the diff output that exunit provides. However, that just added some more whitespace to a single line diff between the two SQL queries. Here’s an example:

What I would like is to have a test failure that would wrap on the newlines instead. Ideally, it would look something like this with the diff coloring elixir does:

  1) test formats SQL (SQLFormatterTest)
     test/sql_formatter_test.exs:5
     Assertion with == failed
     code:  assert SQLFormatter.format("SELECT * FROM users") == "SELECT\n  *\nFROM\n  user\nWHERE\n  id = 1\n"
     left:  """
            SELECT
              *
            FROM
              users
            """
     right: """
            SELECT
              *
            FROM
              user
            WHERE
              id = 1
            """
     stacktrace:
       test/sql_formatter_test.exs:6: (test)

I’m curious if anyone has any suggestions for how to get better test output?

Marked As Solved

sodapopcan

sodapopcan

The problem I run into here is that format_test_failure seems to escape \n as \\n. AFAIK (and I would love to be proved wrong) I think you have to do the formatting yourself.

This is a cobbled together version from a formatter I have. I was hooking into the standard formatter and using format_test_failure so I had to remove that which of course removes all the meta data like test name and line number (though it’s easily added as all of that info is available between error and meta). All this has is left and right but it does work if you want to use it as a starting point!

Again, hopefully I’m wrong and there is an easier way.

defmodule Formatter do
  use GenServer

  def init(_opts) do
    {:ok, %{failures: []}}
  end

  def handle_cast({:test_finished, %{state: {:failed, errors}} = _test} = _event, state) do
    error =
      errors
      |> Enum.map(fn {:error, error, _meta} ->
        left =
          String.split(error.left, "\n")
          |> Enum.map(& "      " <> &1)
          |> Enum.join("\n")

        right =
          String.split(error.right, "\n")
          |> Enum.map(&"       " <> &1)
          |> Enum.join("\n")

        "left: \"\"\"\n" <> left <> "\"\"\"\n\nright: \"\"\"\n" <> right <> "\"\"\""
      end)
      |> Enum.join("\n")

    state = %{state | failures: [error | state.failures]}

    {:noreply, state}
  end

 def handle_cast({:suite_finished, _} = _event, state) do
   if Enum.any?(state.failures) do
     failures =
       state.failures
       |> Enum.reverse()
       |> Enum.join("\n")

     IO.puts("""

       Failures:

     #{failures}
     """)
   end

   {:noreply, state}
 end

  def handle_cast(_event, state) do
    {:noreply, state}
  end
end

ExUnit.start(formatters: [Formatter])

defmodule TestIt do
  use ExUnit.Case

  test "thing" do
    assert """
    line 1
    line 2
    line 3
    """ == """
    line oops
    line 2
    line 3
    """
  end
end

Also Liked

egze

egze

I’ve been doing similar things at work for showing meaningful diffs between 2 JSON files. I had to normalize both: sort keys (json keys don’t have order), pretty print, and only then diff.

See these links for inspiration:

  1. Semantic Diff for SQL
  2. pg_query fingerprint
  3. difftastic for SQL
codeanpeace

codeanpeace

It sounds like something you could do with the ExUnit.Formatter module.

format_test_failure(test, failures, counter, width, formatter)
Receives a test and formats its failures.

Examples

iex> failure = {:error, catch_error(raise "oops"), _stacktrace = []}
iex> formatter_cb = fn _key, value -> value end
iex> test = %ExUnit.Test{name: :"it works", module: MyTest, tags: %{file: "file.ex", line: 7}}
iex> format_test_failure(test, [failure], 1, 80, formatter_cb)
"  1) it works (MyTest)\n     file.ex:7\n     ** (RuntimeError) oops\n"

source: docs for format_test_failure/5

blatyo

blatyo

Conduit Core Team

Thanks for the suggestions. I was definitely in the headspace of thinking I’d have to write a custom assertion. I forgot ex_unit even had formatters, so that will be a useful area to explore.

Where Next?

Popular in Questions Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New

We're in Beta

About us Mission Statement