1Code.require_file("../../test_helper.exs", __DIR__) 2 3defmodule Mix.Tasks.TestTest do 4 use MixTest.Case 5 6 describe "ex_unit_opts/1" do 7 test "returns ex unit options" do 8 assert ex_unit_opts_from_given(unknown: "ok", seed: 13) == [seed: 13] 9 end 10 11 test "returns includes and excludes" do 12 included = [include: [:focus, key: "val"]] 13 assert ex_unit_opts_from_given(include: "focus", include: "key:val") == included 14 15 excluded = [exclude: [:focus, key: "val"]] 16 assert ex_unit_opts_from_given(exclude: "focus", exclude: "key:val") == excluded 17 end 18 19 test "translates :only into includes and excludes" do 20 assert ex_unit_opts_from_given(only: "focus") == [include: [:focus], exclude: [:test]] 21 22 only = [include: [:focus, :special], exclude: [:test]] 23 assert ex_unit_opts_from_given(only: "focus", include: "special") == only 24 end 25 26 test "translates :color into list containing an enabled key-value pair" do 27 assert ex_unit_opts_from_given(color: false) == [colors: [enabled: false]] 28 assert ex_unit_opts_from_given(color: true) == [colors: [enabled: true]] 29 end 30 31 test "translates :formatter into list of modules" do 32 assert ex_unit_opts_from_given(formatter: "A.B") == [formatters: [A.B]] 33 end 34 35 test "accepts custom :exit_status" do 36 assert {:exit_status, 5} in ex_unit_opts(exit_status: 5) 37 end 38 39 test "includes some default options" do 40 assert ex_unit_opts([]) == [ 41 autorun: false, 42 exit_status: 2, 43 failures_manifest_file: 44 Path.join(Mix.Project.manifest_path(), ".mix_test_failures") 45 ] 46 end 47 48 defp ex_unit_opts(opts) do 49 {ex_unit_opts, _allowed_files} = Mix.Tasks.Test.process_ex_unit_opts(opts) 50 ex_unit_opts 51 end 52 53 defp ex_unit_opts_from_given(passed) do 54 passed 55 |> ex_unit_opts() 56 |> Keyword.drop([:failures_manifest_file, :autorun, :exit_status]) 57 end 58 end 59 60 describe "--stale" do 61 test "runs all tests for first run, then none on second" do 62 in_fixture("test_stale", fn -> 63 assert_stale_run_output("2 tests, 0 failures") 64 65 assert_stale_run_output(""" 66 No stale tests 67 """) 68 end) 69 end 70 71 test "runs tests that depend on modified modules" do 72 in_fixture("test_stale", fn -> 73 assert_stale_run_output("2 tests, 0 failures") 74 75 set_all_mtimes() 76 force_recompilation("lib/b.ex") 77 78 assert_stale_run_output("1 test, 0 failures") 79 80 set_all_mtimes() 81 force_recompilation("lib/a.ex") 82 83 assert_stale_run_output("2 tests, 0 failures") 84 end) 85 end 86 87 test "doesn't write manifest when there are failures" do 88 in_fixture("test_stale", fn -> 89 assert_stale_run_output("2 tests, 0 failures") 90 91 set_all_mtimes() 92 93 File.write!("lib/b.ex", """ 94 defmodule B do 95 def f, do: :error 96 end 97 """) 98 99 assert_stale_run_output("1 test, 1 failure") 100 101 assert_stale_run_output("1 test, 1 failure") 102 end) 103 end 104 105 test "runs tests that have changed" do 106 in_fixture("test_stale", fn -> 107 assert_stale_run_output("2 tests, 0 failures") 108 109 set_all_mtimes() 110 File.touch!("test/a_test_stale.exs") 111 112 assert_stale_run_output("1 test, 0 failures") 113 end) 114 end 115 116 test "runs tests that have changed test_helpers" do 117 in_fixture("test_stale", fn -> 118 assert_stale_run_output("2 tests, 0 failures") 119 120 set_all_mtimes() 121 File.touch!("test/test_helper.exs") 122 123 assert_stale_run_output("2 tests, 0 failures") 124 end) 125 end 126 127 test "runs all tests no matter what with --force" do 128 in_fixture("test_stale", fn -> 129 assert_stale_run_output("2 tests, 0 failures") 130 131 assert_stale_run_output(~w[--force], "2 tests, 0 failures") 132 end) 133 end 134 end 135 136 describe "--cover" do 137 test "reports the coverage of each app's modules in an umbrella" do 138 in_fixture("umbrella_test", fn -> 139 # This fixture by default results in coverage above the default threshold 140 # which should result in an exit status of 0. 141 assert {output, 0} = mix_code(["test", "--cover"]) 142 assert output =~ "4 tests, 0 failures" 143 144 # For bar, we do regular --cover and also test protocols 145 assert output =~ """ 146 Generating cover results ... 147 148 Percentage | Module 149 -----------|-------------------------- 150 100.00% | Bar.Protocol 151 100.00% | Bar.Protocol.BitString 152 -----------|-------------------------- 153 100.00% | Total 154 """ 155 156 assert output =~ "1 test, 0 failures" 157 158 # For foo, we do regular --cover and test it does not include bar 159 assert output =~ """ 160 Generating cover results ... 161 162 Percentage | Module 163 -----------|-------------------------- 164 100.00% | Foo 165 -----------|-------------------------- 166 100.00% | Total 167 """ 168 169 # We skip a test in bar to force coverage below the default threshold 170 # which should result in an exit status of 1. 171 assert {_, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"]) 172 173 unless windows?() do 174 assert code == 3 175 end 176 end) 177 end 178 179 test "supports unified reports by using test.coverage" do 180 in_fixture("umbrella_test", fn -> 181 assert mix(["test", "--export-coverage", "default", "--cover"]) =~ 182 "Run \"mix test.coverage\" once all exports complete" 183 184 assert mix(["test.coverage"]) =~ """ 185 Importing cover results: apps/bar/cover/default.coverdata 186 Importing cover results: apps/foo/cover/default.coverdata 187 188 Percentage | Module 189 -----------|-------------------------- 190 100.00% | Bar 191 100.00% | Bar.Ignore 192 100.00% | Bar.Protocol 193 100.00% | Bar.Protocol.BitString 194 100.00% | Foo 195 -----------|-------------------------- 196 100.00% | Total 197 """ 198 end) 199 end 200 end 201 202 describe "--failed" do 203 test "loads only files with failures and runs just the failures" do 204 in_fixture("test_failed", fn -> 205 loading_only_passing_test_msg = "loading OnlyPassingTest" 206 207 # Run `mix test` once to record failures... 208 output = mix(["test"]) 209 assert output =~ loading_only_passing_test_msg 210 assert output =~ "4 tests, 2 failures" 211 212 # `mix test --failed` runs only failed tests and avoids loading files with no failures 213 output = mix(["test", "--failed"]) 214 refute output =~ loading_only_passing_test_msg 215 assert output =~ "2 tests, 2 failures" 216 217 # `mix test --failed` can be applied to a directory or file 218 output = mix(["test", "test/passing_and_failing_test_failed.exs", "--failed"]) 219 assert output =~ "1 test, 1 failure" 220 221 # `--failed` composes with an `--only` filter by running the intersection. 222 # Of the failing tests, 1 is tagged with `@tag :foo`. 223 # Of the passing tests, 1 is tagged with `@tag :foo`. 224 # But only the failing test with that tag should run. 225 output = mix(["test", "--failed", "--only", "foo"]) 226 assert output =~ "2 tests, 1 failure, 1 excluded" 227 228 # Run again to give it a chance to record as passed 229 System.put_env("PASS_FAILING_TESTS", "true") 230 assert mix(["test", "--failed"]) =~ "2 tests, 0 failures" 231 232 # Nothing should get run if we try it again since everything is passing. 233 assert mix(["test", "--failed"]) =~ "There are no tests to run" 234 235 # `--failed` and `--stale` cannot be combined 236 output = mix(["test", "--failed", "--stale"]) 237 assert output =~ "Combining --failed and --stale is not supported" 238 end) 239 after 240 System.delete_env("PASS_FAILING_TESTS") 241 end 242 end 243 244 describe "--listen-on-stdin" do 245 test "runs tests after input" do 246 in_fixture("test_stale", fn -> 247 port = mix_port(~w[test --stale --listen-on-stdin]) 248 249 assert receive_until_match(port, "seed", "") =~ "2 tests" 250 251 Port.command(port, "\n") 252 253 assert receive_until_match(port, "No stale tests", "") =~ "Restarting..." 254 end) 255 end 256 257 test "does not exit on compilation failure" do 258 in_fixture("test_stale", fn -> 259 File.write!("lib/b.ex", """ 260 defmodule B do 261 def f, do: error_not_a_var 262 end 263 """) 264 265 port = mix_port(~w[test --listen-on-stdin]) 266 267 assert receive_until_match(port, "error", "") =~ "lib/b.ex" 268 269 File.write!("lib/b.ex", """ 270 defmodule B do 271 def f, do: A.f 272 end 273 """) 274 275 Port.command(port, "\n") 276 277 assert receive_until_match(port, "seed", "") =~ "2 tests" 278 279 File.write!("test/b_test_stale.exs", """ 280 defmodule BTest do 281 use ExUnit.Case 282 283 test "f" do 284 assert B.f() == error_not_a_var 285 end 286 end 287 """) 288 289 Port.command(port, "\n") 290 291 message = "undefined function error_not_a_var" 292 assert receive_until_match(port, message, "") =~ "test/b_test_stale.exs" 293 294 File.write!("test/b_test_stale.exs", """ 295 defmodule BTest do 296 use ExUnit.Case 297 298 test "f" do 299 assert B.f() == :ok 300 end 301 end 302 """) 303 304 Port.command(port, "\n") 305 306 assert receive_until_match(port, "seed", "") =~ "2 tests" 307 end) 308 end 309 end 310 311 describe "--partitions" do 312 test "splits tests into partitions" do 313 in_fixture("test_stale", fn -> 314 assert mix(["test", "--partitions", "3", "--cover"], [{"MIX_TEST_PARTITION", "1"}]) =~ 315 "1 test, 0 failures" 316 317 assert mix(["test", "--partitions", "3", "--cover"], [{"MIX_TEST_PARTITION", "2"}]) =~ 318 "1 test, 0 failures" 319 320 assert mix(["test", "--partitions", "3", "--cover"], [{"MIX_TEST_PARTITION", "3"}]) =~ 321 "There are no tests to run" 322 323 assert File.regular?("cover/1.coverdata") 324 assert File.regular?("cover/2.coverdata") 325 refute File.regular?("cover/3.coverdata") 326 327 assert mix(["test.coverage"]) == """ 328 Importing cover results: cover/1.coverdata 329 Importing cover results: cover/2.coverdata 330 331 Percentage | Module 332 -----------|-------------------------- 333 100.00% | A 334 100.00% | B 335 -----------|-------------------------- 336 100.00% | Total 337 338 Generated HTML coverage results in \"cover\" directory 339 """ 340 end) 341 end 342 343 test "raises when no partition is given even with Mix.shell() change" do 344 in_fixture("test_stale", fn -> 345 File.write!("test/test_helper.exs", """ 346 Mix.shell(Mix.Shell.Process) 347 ExUnit.start() 348 """) 349 350 assert_run_output( 351 ["--partitions", "4"], 352 "The MIX_TEST_PARTITION environment variable must be set" 353 ) 354 end) 355 end 356 357 test "do not raise if partitions flag is set to 1 and no partition given" do 358 in_fixture("test_stale", fn -> 359 assert mix(["test", "--partitions", "1"], []) =~ 360 "2 tests, 0 failures" 361 362 assert mix(["test", "--partitions", "1"], [{"MIX_TEST_PARTITION", ""}]) =~ 363 "2 tests, 0 failures" 364 365 assert mix(["test", "--partitions", "1"], [{"MIX_TEST_PARTITION", "1"}]) =~ 366 "2 tests, 0 failures" 367 end) 368 end 369 370 test "raise if partitions is set to non-positive value" do 371 in_fixture("test_stale", fn -> 372 File.write!("test/test_helper.exs", """ 373 Mix.shell(Mix.Shell.Process) 374 ExUnit.start() 375 """) 376 377 assert_run_output( 378 ["--partitions", "0"], 379 "--partitions : expected to be positive integer, got 0" 380 ) 381 382 assert_run_output( 383 ["--partitions", "-1"], 384 "--partitions : expected to be positive integer, got -1" 385 ) 386 end) 387 end 388 end 389 390 describe "logs and errors" do 391 test "logs test absence for a project with no test paths" do 392 in_fixture("test_stale", fn -> 393 File.rm_rf!("test") 394 395 assert_run_output("There are no tests to run") 396 end) 397 end 398 399 test "raises when no test runs even with Mix.shell() change" do 400 in_fixture("test_stale", fn -> 401 File.write!("test/test_helper.exs", """ 402 Mix.shell(Mix.Shell.Process) 403 ExUnit.start() 404 """) 405 406 assert_run_output( 407 ["--only", "unknown"], 408 "The --only option was given to \"mix test\" but no test was executed" 409 ) 410 end) 411 end 412 413 test "raises an exception if line numbers are given with multiple files" do 414 in_fixture("test_stale", fn -> 415 assert_run_output( 416 [ 417 "test/a_test_stale.exs", 418 "test/b_test_stale.exs:4" 419 ], 420 "Line numbers can only be used when running a single test file" 421 ) 422 end) 423 end 424 425 test "umbrella with file path" do 426 in_fixture("umbrella_test", fn -> 427 # Run false positive test first so at least the code is compiled 428 # and we can perform more aggressive assertions later 429 assert mix(["test", "apps/unknown_app/test"]) =~ """ 430 ==> bar 431 Paths given to "mix test" did not match any directory/file: apps/unknown_app/test 432 ==> foo 433 Paths given to "mix test" did not match any directory/file: apps/unknown_app/test 434 """ 435 436 output = mix(["test", "apps/bar/test/bar_tests.exs"]) 437 438 assert output =~ """ 439 ==> bar 440 .... 441 """ 442 443 refute output =~ "==> foo" 444 refute output =~ "Paths given to \"mix test\" did not match any directory/file" 445 446 output = mix(["test", "apps/bar/test/bar_tests.exs:10"]) 447 448 assert output =~ """ 449 ==> bar 450 Excluding tags: [:test] 451 Including tags: [line: \"10\"] 452 453 . 454 """ 455 456 refute output =~ "==> foo" 457 refute output =~ "Paths given to \"mix test\" did not match any directory/file" 458 end) 459 end 460 end 461 462 describe "--warnings-as-errors" do 463 test "fail on warning in tests" do 464 in_fixture("test_stale", fn -> 465 msg = 466 "Test suite aborted after successful execution due to warnings while using the --warnings-as-errors option" 467 468 refute mix(["test", "--warnings-as-errors"]) =~ msg 469 470 File.write!("lib/warning.ex", """ 471 unused_compile_var = 1 472 """) 473 474 File.write!("test/warning_test_stale.exs", """ 475 defmodule WarningTest do 476 use ExUnit.Case 477 478 test "warning" do 479 unused_test_var = 1 480 end 481 end 482 """) 483 484 output = mix(["test", "--warnings-as-errors", "test/warning_test_stale.exs"]) 485 assert output =~ "variable \"unused_compile_var\" is unused" 486 assert output =~ "variable \"unused_test_var\" is unused" 487 assert output =~ msg 488 end) 489 end 490 491 test "mark failed tests" do 492 in_fixture("test_failed", fn -> 493 File.write!("test/warning_test_failed.exs", """ 494 defmodule WarningTest do 495 use ExUnit.Case 496 497 test "warning" do 498 unused_var = 123 499 end 500 end 501 """) 502 503 output = mix(["test", "--warnings-as-errors"]) 504 assert output =~ "2 failures" 505 refute output =~ "Test suite aborted after successful execution" 506 output = mix(["test", "--failed"]) 507 assert output =~ "2 failures" 508 end) 509 end 510 end 511 512 describe "--exit-status" do 513 @describetag :unix 514 515 test "returns custom exit status" do 516 in_fixture("test_failed", fn -> 517 {output, exit_status} = mix_code(["test", "--exit-status", "5"]) 518 assert output =~ "2 failures" 519 assert exit_status == 5 520 end) 521 end 522 end 523 524 defp receive_until_match(port, expected, acc) do 525 receive do 526 {^port, {:data, output}} -> 527 acc = acc <> output 528 529 if output =~ expected do 530 acc 531 else 532 receive_until_match(port, expected, acc) 533 end 534 end 535 end 536 537 defp set_all_mtimes(time \\ {{2010, 1, 1}, {0, 0, 0}}) do 538 Enum.each(Path.wildcard("**", match_dot: true), &File.touch!(&1, time)) 539 end 540 541 defp assert_stale_run_output(opts \\ [], expected) do 542 assert_run_output(["--stale" | opts], expected) 543 end 544 545 defp assert_run_output(opts \\ [], expected) do 546 assert mix(["test" | opts]) =~ expected 547 end 548end 549