1Code.require_file("../test_helper.exs", __DIR__) 2 3defmodule ExUnit.FormatterTest do 4 use ExUnit.Case 5 6 import ExUnit.Formatter 7 doctest ExUnit.Formatter 8 9 defmacrop catch_assertion(expr) do 10 quote do 11 try do 12 unquote(expr) 13 rescue 14 ex -> ex 15 end 16 end 17 end 18 19 defp test_module do 20 %ExUnit.TestModule{name: Hello} 21 end 22 23 defp test do 24 %ExUnit.Test{name: :world, module: Hello, tags: %{file: __ENV__.file, line: 1}} 25 end 26 27 def falsy() do 28 false 29 end 30 31 defp formatter(_key, value), do: value 32 33 defp diff_formatter(:diff_enabled?, _default), do: true 34 defp diff_formatter(_key, value), do: value 35 36 test "formats test case filters" do 37 filters = [run: true, slow: false] 38 assert format_filters(filters, :exclude) =~ "Excluding tags: [run: true, slow: false]" 39 assert format_filters(filters, :include) =~ "Including tags: [run: true, slow: false]" 40 end 41 42 test "formats test errors" do 43 failure = [{:error, catch_error(raise "oops"), []}] 44 45 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 46 1) world (Hello) 47 test/ex_unit/formatter_test.exs:1 48 ** (RuntimeError) oops 49 """ 50 end 51 52 test "formats test exits" do 53 failure = [{:exit, 1, []}] 54 55 assert format_test_failure(test(), failure, 1, 80, &formatter/2) == """ 56 1) world (Hello) 57 test/ex_unit/formatter_test.exs:1 58 ** (exit) 1 59 """ 60 end 61 62 test "formats test exits with mfa" do 63 failure = [{:exit, {:bye, {:mod, :fun, []}}, []}] 64 65 assert format_test_failure(test(), failure, 1, 80, &formatter/2) == """ 66 1) world (Hello) 67 test/ex_unit/formatter_test.exs:1 68 ** (exit) exited in: :mod.fun() 69 ** (EXIT) :bye 70 """ 71 end 72 73 test "formats test exits with function clause mfa" do 74 {error, stack} = 75 try do 76 Access.fetch(:foo, :bar) 77 catch 78 :error, error -> {error, __STACKTRACE__} 79 end 80 81 failure = [{:exit, {{error, stack}, {:mod, :fun, []}}, []}] 82 83 assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~ 84 """ 85 1) world (Hello) 86 test/ex_unit/formatter_test.exs:1 87 ** (exit) exited in: :mod.fun() 88 ** (EXIT) an exception was raised: 89 ** (FunctionClauseError) no function clause matching in Access.fetch/2 90 91 The following arguments were given to Access.fetch/2: 92 93 # 1 94 :foo 95 96 # 2 97 :bar 98 99 Attempted function clauses (showing 5 out of 5): 100 101 def fetch(%module{} = container, key) 102 """ 103 end 104 105 test "formats test exits with assertion mfa" do 106 {error, stack} = 107 try do 108 assert 1 == 2 109 rescue 110 error -> {error, __STACKTRACE__} 111 end 112 113 failure = [{:exit, {{error, stack}, {:mod, :fun, []}}, []}] 114 115 assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~ 116 """ 117 1) world (Hello) 118 test/ex_unit/formatter_test.exs:1 119 ** (exit) exited in: :mod.fun() 120 ** (EXIT) an exception was raised: 121 Assertion with == failed 122 code: assert 1 == 2 123 left: 1 124 right: 2 125 """ 126 end 127 128 test "formats test throws" do 129 failure = [{:throw, 1, []}] 130 131 assert format_test_failure(test(), failure, 1, 80, &formatter/2) == """ 132 1) world (Hello) 133 test/ex_unit/formatter_test.exs:1 134 ** (throw) 1 135 """ 136 end 137 138 test "formats test EXITs" do 139 failure = [{{:EXIT, self()}, 1, []}] 140 141 assert format_test_failure(test(), failure, 1, 80, &formatter/2) == """ 142 1) world (Hello) 143 test/ex_unit/formatter_test.exs:1 144 ** (EXIT from #{inspect(self())}) 1 145 """ 146 end 147 148 test "formats test EXITs with function clause errors" do 149 {error, stack} = 150 try do 151 Access.fetch(:foo, :bar) 152 catch 153 :error, error -> {error, __STACKTRACE__} 154 end 155 156 failure = [{{:EXIT, self()}, {error, stack}, []}] 157 format = trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) 158 159 assert format =~ 160 """ 161 1) world (Hello) 162 test/ex_unit/formatter_test.exs:1 163 ** (EXIT from #{inspect(self())}) an exception was raised: 164 165 ** (FunctionClauseError) no function clause matching in Access.fetch/2 166 167 The following arguments were given to Access.fetch/2: 168 169 # 1 170 :foo 171 172 # 2 173 :bar 174 175 Attempted function clauses (showing 5 out of 5): 176 177 def fetch(%module{} = container, key) 178 """ 179 180 assert format =~ ~r"lib/access.ex:\d+: Access.fetch/2" 181 end 182 183 test "formats test EXITs with assertion errors" do 184 {error, stack} = 185 try do 186 assert 1 == 2 187 rescue 188 error -> {error, __STACKTRACE__} 189 end 190 191 failure = [{{:EXIT, self()}, {error, stack}, []}] 192 193 assert trim_multiline_whitespace(format_test_failure(test(), failure, 1, 80, &formatter/2)) =~ 194 """ 195 1) world (Hello) 196 test/ex_unit/formatter_test.exs:1 197 ** (EXIT from #{inspect(self())}) an exception was raised: 198 199 Assertion with == failed 200 code: assert 1 == 2 201 left: 1 202 right: 2 203 """ 204 end 205 206 test "formats test errors with test_location_relative_path" do 207 Application.put_env(:ex_unit, :test_location_relative_path, "apps/sample") 208 failure = [{:error, catch_error(raise "oops"), []}] 209 210 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 211 1) world (Hello) 212 apps/sample/test/ex_unit/formatter_test.exs:1 213 ** (RuntimeError) oops 214 """ 215 after 216 Application.delete_env(:ex_unit, :test_location_relative_path) 217 end 218 219 test "formats test errors with code snippets" do 220 stack = {Hello, :world, 1, [file: __ENV__.file, line: 3]} 221 failure = [{:error, catch_error(raise "oops"), [stack]}] 222 223 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 224 1) world (Hello) 225 test/ex_unit/formatter_test.exs:1 226 ** (RuntimeError) oops 227 code: defmodule ExUnit.FormatterTest do 228 """ 229 end 230 231 test "formats stacktraces" do 232 stacktrace = [{Oops, :wrong, 1, [file: "formatter_test.exs", line: 1]}] 233 failure = [{:error, catch_error(raise "oops"), stacktrace}] 234 235 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 236 1) world (Hello) 237 test/ex_unit/formatter_test.exs:1 238 ** (RuntimeError) oops 239 stacktrace: 240 formatter_test.exs:1: Oops.wrong/1 241 """ 242 end 243 244 test "formats assertions" do 245 failure = [{:error, catch_assertion(assert ExUnit.FormatterTest.falsy()), []}] 246 247 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 248 1) world (Hello) 249 test/ex_unit/formatter_test.exs:1 250 Expected truthy, got false 251 code: assert ExUnit.FormatterTest.falsy() 252 """ 253 end 254 255 test "formats assertions with patterns and values" do 256 failure = [{:error, catch_assertion(assert {1, 2, 3} > {1, 2, 3}), []}] 257 258 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 259 1) world (Hello) 260 test/ex_unit/formatter_test.exs:1 261 Assertion with > failed, both sides are exactly equal 262 code: assert {1, 2, 3} > {1, 2, 3} 263 left: {1, 2, 3} 264 """ 265 266 failure = [{:error, catch_assertion(assert {3, 2, 1} = {1, 2, 3}), []}] 267 268 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 269 1) world (Hello) 270 test/ex_unit/formatter_test.exs:1 271 match (=) failed 272 code: assert {3, 2, 1} = {1, 2, 3} 273 left: {3, 2, 1} 274 right: {1, 2, 3} 275 """ 276 end 277 278 nfc_hello = String.normalize("héllo", :nfc) 279 nfd_hello = String.normalize("héllo", :nfd) 280 281 test "formats assertions with hints" do 282 failure = [{:error, catch_assertion(assert unquote(nfc_hello) == unquote(nfd_hello)), []}] 283 284 assert format_test_failure(test(), failure, 1, 80, &diff_formatter/2) =~ """ 285 1) world (Hello) 286 test/ex_unit/formatter_test.exs:1 287 Assertion with == failed 288 code: assert "#{unquote(nfc_hello)}" == "#{unquote(nfd_hello)}" 289 left: "#{unquote(nfc_hello)}" 290 right: "#{unquote(nfd_hello)}" 291 hint: you are comparing strings that have the same visual representation but are made of different Unicode codepoints 292 """ 293 end 294 295 test "formats multiple assertions" do 296 failure = [ 297 {:error, catch_assertion(assert ExUnit.FormatterTest.falsy()), []}, 298 {:error, catch_assertion(assert 1 == 2), []} 299 ] 300 301 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 302 1) world (Hello) 303 test/ex_unit/formatter_test.exs:1 304 305 Failure #1 306 Expected truthy, got false 307 code: assert ExUnit.FormatterTest.falsy() 308 309 Failure #2 310 Assertion with == failed 311 code: assert 1 == 2 312 left: 1 313 right: 2 314 """ 315 end 316 317 defp trim_multiline_whitespace(string) do 318 String.replace(string, ~r"\n\s+\n", "\n\n") 319 end 320 321 test "blames function clause error" do 322 {error, stack} = 323 try do 324 Access.fetch(:foo, :bar) 325 rescue 326 exception -> {exception, __STACKTRACE__} 327 end 328 329 failure = format_test_failure(test(), [{:error, error, [hd(stack)]}], 1, 80, &formatter/2) 330 331 assert trim_multiline_whitespace(failure) =~ """ 332 1) world (Hello) 333 test/ex_unit/formatter_test.exs:1 334 ** (FunctionClauseError) no function clause matching in Access.fetch/2 335 336 The following arguments were given to Access.fetch/2: 337 338 # 1 339 :foo 340 341 # 2 342 :bar 343 344 Attempted function clauses (showing 5 out of 5): 345 346 def fetch(%module{} = container, key) 347 """ 348 349 assert failure =~ ~r"\(elixir #{System.version()}\) lib/access\.ex:\d+: Access\.fetch/2" 350 end 351 352 test "formats setup_all errors" do 353 failure = [{:error, catch_error(raise "oops"), []}] 354 355 assert format_test_all_failure(test_module(), failure, 1, 80, &formatter/2) =~ """ 356 1) Hello: failure on setup_all callback, all tests have been invalidated 357 ** (RuntimeError) oops 358 """ 359 end 360 361 test "formats matches correctly" do 362 failure = [{:error, catch_assertion(assert %{a: :b} = %{a: :c}), []}] 363 364 assert format_test_all_failure(test_module(), failure, 1, :infinity, &formatter/2) =~ """ 365 1) Hello: failure on setup_all callback, all tests have been invalidated 366 match (=) failed 367 code: assert %{a: :b} = %{a: :c} 368 left: %{a: :b} 369 right: %{a: :c} 370 """ 371 end 372 373 test "formats assertions with operators with no limit" do 374 failure = [{:error, catch_assertion(assert [1, 2, 3] == [4, 5, 6]), []}] 375 376 assert format_test_all_failure(test_module(), failure, 1, :infinity, &formatter/2) =~ """ 377 1) Hello: failure on setup_all callback, all tests have been invalidated 378 Assertion with == failed 379 code: assert [1, 2, 3] == [4, 5, 6] 380 left: [1, 2, 3] 381 right: [4, 5, 6] 382 """ 383 end 384 385 test "formats assertions with operators with column limit" do 386 failure = [{:error, catch_assertion(assert [1, 2, 3] == [4, 5, 6]), []}] 387 388 assert format_test_all_failure(test_module(), failure, 1, 15, &formatter/2) =~ """ 389 1) Hello: failure on setup_all callback, all tests have been invalidated 390 Assertion with == failed 391 code: assert [1, 2, 3] == [4, 5, 6] 392 left: [1, 393 2, 394 3] 395 right: [4, 396 5, 397 6] 398 """ 399 end 400 401 test "formats assertions with complex function call arguments" do 402 failure = [{:error, catch_assertion(assert is_list(List.to_tuple([1, 2, 3]))), []}] 403 404 assert format_test_all_failure(test_module(), failure, 1, 80, &formatter/2) =~ """ 405 1) Hello: failure on setup_all callback, all tests have been invalidated 406 Expected truthy, got false 407 code: assert is_list(List.to_tuple([1, 2, 3])) 408 arguments: 409 410 # 1 411 {1, 2, 3} 412 """ 413 414 failure = [{:error, catch_assertion(assert is_list({1, 2})), []}] 415 416 assert format_test_all_failure(test_module(), failure, 1, 80, &formatter/2) =~ """ 417 1) Hello: failure on setup_all callback, all tests have been invalidated 418 Expected truthy, got false 419 code: assert is_list({1, 2}) 420 """ 421 end 422 423 test "formats assertions with message with multiple lines" do 424 message = "Some meaningful error:\nuseful info\nanother useful info" 425 failure = [{:error, catch_assertion(assert(false, message)), []}] 426 427 assert format_test_all_failure(test_module(), failure, 1, :infinity, &formatter/2) =~ """ 428 1) Hello: failure on setup_all callback, all tests have been invalidated 429 Some meaningful error: 430 useful info 431 another useful info 432 """ 433 end 434 435 defmodule BadInspect do 436 defstruct key: 0 437 438 defimpl Inspect do 439 def inspect(struct, opts) when is_atom(opts) do 440 struct.unknown 441 end 442 end 443 end 444 445 test "inspect failure" do 446 failure = [{:error, catch_assertion(assert :will_fail == %BadInspect{}), []}] 447 448 message = 449 "got FunctionClauseError with message \"no function clause matching " <> 450 "in Inspect.ExUnit.FormatterTest.BadInspect.inspect/2\" while inspecting " <> 451 "%{__struct__: ExUnit.FormatterTest.BadInspect, key: 0}" 452 453 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 454 1) world (Hello) 455 test/ex_unit/formatter_test.exs:1 456 Assertion with == failed 457 code: assert :will_fail == %BadInspect{} 458 left: :will_fail 459 right: %Inspect.Error{ 460 message: #{inspect(message)} 461 } 462 """ 463 end 464 465 defmodule BadMessage do 466 defexception key: 0 467 468 @impl true 469 def message(_message) do 470 raise "oops" 471 end 472 end 473 474 test "message failure" do 475 failure = [{:error, catch_error(raise BadMessage), []}] 476 477 message = 478 "got RuntimeError with message \"oops\" while retrieving Exception.message/1 " <> 479 "for %ExUnit.FormatterTest.BadMessage{key: 0}. Stacktrace:" 480 481 assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """ 482 1) world (Hello) 483 test/ex_unit/formatter_test.exs:1 484 ** (ExUnit.FormatterTest.BadMessage) #{message} 485 """ 486 end 487end 488