[INFO] cloning repository https://github.com/jcholder-thoughtworks/ray_tracer_challenge
[INFO] running `Command { std: "git" "-c" "credential.helper=" "-c" "credential.helper=/workspace/cargo-home/bin/git-credential-null" "clone" "--bare" "https://github.com/jcholder-thoughtworks/ray_tracer_challenge" "/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fjcholder-thoughtworks%2Fray_tracer_challenge", kill_on_drop: false }`
[INFO] [stderr] Cloning into bare repository '/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fjcholder-thoughtworks%2Fray_tracer_challenge'...
[INFO] running `Command { std: "git" "rev-parse" "HEAD", kill_on_drop: false }`
[INFO] [stdout] a76a30dcbb16137910b5f2cc66e452c11c4ef40e
[INFO] testing jcholder-thoughtworks/ray_tracer_challenge against 1.98.0-beta.1 for beta-1.98-2
[INFO] running `Command { std: "git" "clone" "/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fjcholder-thoughtworks%2Fray_tracer_challenge" "/workspace/builds/worker-4-tc2/source", kill_on_drop: false }`
[INFO] [stderr] Cloning into '/workspace/builds/worker-4-tc2/source'...
[INFO] [stderr] done.
[INFO] started tweaking git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge
[INFO] removed 0 missing tests
[INFO] finished tweaking git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge
[INFO] tweaked toml for git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge written to /workspace/builds/worker-4-tc2/source/Cargo.toml
[INFO] validating manifest of git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge on toolchain 1.98.0-beta.1
[INFO] running `Command { std: CARGO_HOME="/workspace/cargo-home" RUSTUP_HOME="/workspace/rustup-home" "/workspace/cargo-home/bin/cargo" "+1.98.0-beta.1" "metadata" "--manifest-path" "Cargo.toml" "--no-deps", kill_on_drop: false }`
[INFO] crate git repo https://github.com/jcholder-thoughtworks/ray_tracer_challenge already has a lockfile, it will not be regenerated
[INFO] running `Command { std: CARGO_HOME="/workspace/cargo-home" RUSTUP_HOME="/workspace/rustup-home" "/workspace/cargo-home/bin/cargo" "+1.98.0-beta.1" "fetch" "--manifest-path" "Cargo.toml", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-4-tc2/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-4-tc2/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-m" "1610612736" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:3d5ced03c013a94a2f102a4510f48a6e9184255caf5fd8244f58017bde7f5210" "sleep" "infinity", kill_on_drop: false }`
[INFO] [stdout] 54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7
[INFO] running `Command { std: "docker" "start" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "inspect" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-w" "/opt/rustwide/workdir" "--user" "0:0" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7" "/opt/rustwide/cargo-home/bin/cargo" "+1.98.0-beta.1" "metadata" "--no-deps" "--format-version=1", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "inspect" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7" "/opt/rustwide/cargo-home/bin/cargo" "+1.98.0-beta.1" "build" "--frozen" "--message-format=json", kill_on_drop: false }`
[INFO] [stderr]    Compiling ray_tracer_challenge v0.1.0 (/opt/rustwide/workdir)
[INFO] [stdout] warning: unnecessary braces around method argument
[INFO] [stdout]   --> src/lib.rs:86:30
[INFO] [stdout]    |
[INFO] [stdout] 86 |         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stdout]    |                              ^^                      ^^
[INFO] [stdout]    |
[INFO] [stdout]    = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stdout] help: remove these braces
[INFO] [stdout]    |
[INFO] [stdout] 86 -         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stdout] 86 +         self.objs.iter().map(|o| Rc::new(o.clone())).collect()
[INFO] [stdout]    |
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stderr]     Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.23s
[INFO] running `Command { std: "docker" "inspect" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7" "/opt/rustwide/cargo-home/bin/cargo" "+1.98.0-beta.1" "test" "--frozen" "--no-run" "--message-format=json", kill_on_drop: false }`
[INFO] [stderr]    Compiling proc-macro2 v0.4.30
[INFO] [stderr]    Compiling unicode-xid v0.1.0
[INFO] [stderr]    Compiling fnv v1.0.6
[INFO] [stderr]    Compiling memchr v2.3.0
[INFO] [stderr]    Compiling libc v0.2.66
[INFO] [stderr]    Compiling syn v0.15.44
[INFO] [stderr]    Compiling proc-macro2 v1.0.8
[INFO] [stderr]    Compiling cfg-if v0.1.10
[INFO] [stderr]    Compiling strsim v0.7.0
[INFO] [stderr]    Compiling unicode-xid v0.2.0
[INFO] [stderr]    Compiling ident_case v1.0.1
[INFO] [stderr]    Compiling autocfg v0.1.7
[INFO] [stderr]    Compiling syn v1.0.14
[INFO] [stderr]    Compiling ucd-trie v0.1.2
[INFO] [stderr]    Compiling log v0.4.8
[INFO] [stderr]    Compiling aho-corasick v0.7.8
[INFO] [stderr]    Compiling pest v2.1.2
[INFO] [stderr]    Compiling crossbeam-utils v0.7.0
[INFO] [stderr]    Compiling thread_local v1.0.1
[INFO] [stderr]    Compiling maplit v1.0.2
[INFO] [stderr]    Compiling regex-syntax v0.6.14
[INFO] [stderr]    Compiling term_size v0.3.1
[INFO] [stderr]    Compiling bstr v0.2.11
[INFO] [stderr]    Compiling derive_builder v0.7.2
[INFO] [stderr]    Compiling bitflags v1.2.1
[INFO] [stderr]    Compiling quote v0.6.13
[INFO] [stderr]    Compiling quote v1.0.2
[INFO] [stderr]    Compiling unicode-width v0.1.7
[INFO] [stderr]    Compiling pest_meta v2.1.2
[INFO] [stderr]    Compiling textwrap v0.11.0
[INFO] [stderr]    Compiling walkdir v2.3.1
[INFO] [stderr]    Compiling crossbeam-channel v0.4.0
[INFO] [stderr]    Compiling atty v0.2.14
[INFO] [stderr]    Compiling strsim v0.8.0
[INFO] [stderr]    Compiling ansi_term v0.11.0
[INFO] [stderr]    Compiling vec_map v0.8.1
[INFO] [stderr]    Compiling shh v1.0.1
[INFO] [stderr]    Compiling pathdiff v0.1.0
[INFO] [stderr]    Compiling clap v2.33.0
[INFO] [stderr]    Compiling termcolor v1.1.0
[INFO] [stdout] warning: unnecessary braces around method argument
[INFO] [stdout]   --> src/lib.rs:86:30
[INFO] [stdout]    |
[INFO] [stdout] 86 |         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stdout]    |                              ^^                      ^^
[INFO] [stdout]    |
[INFO] [stdout]    = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stdout] help: remove these braces
[INFO] [stdout]    |
[INFO] [stdout] 86 -         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stdout] 86 +         self.objs.iter().map(|o| Rc::new(o.clone())).collect()
[INFO] [stdout]    |
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stderr]    Compiling regex v1.3.4
[INFO] [stderr]    Compiling pest_generator v2.1.1
[INFO] [stderr]    Compiling globset v0.4.4
[INFO] [stderr]    Compiling ignore v0.4.11
[INFO] [stderr]    Compiling pest_derive v2.1.0
[INFO] [stderr]    Compiling globwalk v0.7.1
[INFO] [stderr]    Compiling darling_core v0.9.0
[INFO] [stderr]    Compiling darling_macro v0.9.0
[INFO] [stderr]    Compiling darling v0.9.0
[INFO] [stderr]    Compiling derive_builder_core v0.5.0
[INFO] [stderr]    Compiling gherkin_rust v0.6.0
[INFO] [stderr]    Compiling cucumber_rust v0.6.7
[INFO] [stderr]    Compiling ray_tracer_challenge v0.1.0 (/opt/rustwide/workdir)
[INFO] [stdout] warning: unnecessary braces around method argument
[INFO] [stdout]   --> src/lib.rs:86:30
[INFO] [stdout]    |
[INFO] [stdout] 86 |         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stdout]    |                              ^^                      ^^
[INFO] [stdout]    |
[INFO] [stdout]    = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stdout] help: remove these braces
[INFO] [stdout]    |
[INFO] [stdout] 86 -         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stdout] 86 +         self.objs.iter().map(|o| Rc::new(o.clone())).collect()
[INFO] [stdout]    |
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stderr]     Finished `test` profile [unoptimized + debuginfo] target(s) in 44.38s
[INFO] running `Command { std: "docker" "inspect" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7" "/opt/rustwide/cargo-home/bin/cargo" "+1.98.0-beta.1" "test" "--frozen", kill_on_drop: false }`
[INFO] [stderr] warning: unnecessary braces around method argument
[INFO] [stderr]   --> src/lib.rs:86:30
[INFO] [stderr]    |
[INFO] [stderr] 86 |         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stderr]    |                              ^^                      ^^
[INFO] [stderr]    |
[INFO] [stderr]    = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stderr] help: remove these braces
[INFO] [stderr]    |
[INFO] [stderr] 86 -         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stderr] 86 +         self.objs.iter().map(|o| Rc::new(o.clone())).collect()
[INFO] [stderr]    |
[INFO] [stderr] 
[INFO] [stderr] warning: `ray_tracer_challenge` (lib) generated 1 warning (run `cargo fix --lib -p ray_tracer_challenge` to apply 1 suggestion)
[INFO] [stderr] warning: `ray_tracer_challenge` (lib test) generated 1 warning (1 duplicate)
[INFO] [stderr]     Finished `test` profile [unoptimized + debuginfo] target(s) in 0.16s
[INFO] [stderr]      Running unittests src/lib.rs (/opt/rustwide/target/debug/deps/ray_tracer_challenge-bd5bce0aaaa22866)
[INFO] [stdout] 
[INFO] [stdout] running 29 tests
[INFO] [stdout] test canvas::tests::initialization ... ok
[INFO] [stdout] test canvas::tests::ppm_content ... ok
[INFO] [stdout] test canvas::tests::well_formatted_ppm_content ... ok
[INFO] [stdout] test canvas::tests::terminating_newline_on_ppm_content ... ok
[INFO] [stdout] test canvas::tests::ppm_headers ... ok
[INFO] [stdout] test color::tests::adding_colors ... ok
[INFO] [stdout] test color::tests::multiplying_colors_by_colors ... ok
[INFO] [stdout] test canvas::tests::writing_and_reading_pixels ... ok
[INFO] [stdout] test color::tests::multiplying_colors_by_scalars ... ok
[INFO] [stdout] test color::tests::subtracting_colors ... ok
[INFO] [stdout] test tests::equalish_is_false_for_diff_above_epsilon ... ok
[INFO] [stdout] test tests::equalish_is_true_for_diff_below_epsilon ... ok
[INFO] [stdout] test tests::point_tests::add_vector_sums_each_pair_of_values ... ok
[INFO] [stdout] test tests::point_tests::equalish_is_false_for_diff_above_epsilon ... ok
[INFO] [stdout] test tests::point_tests::equalish_is_true_for_diff_below_epsilon ... ok
[INFO] [stdout] test tests::point_tests::subtract_point_subtracts_latter_from_former_for_each_pair_of_values ... ok
[INFO] [stdout] test tests::point_tests::subtract_vector_subtracts_latter_from_former_for_each_pair_of_values ... ok
[INFO] [stdout] test tests::vector_tests::add_point_sums_each_pair_of_values ... ok
[INFO] [stdout] test tests::vector_tests::add_vector_sums_each_pair_of_values ... ok
[INFO] [stdout] test tests::vector_tests::cross_products_of_vectors ... ok
[INFO] [stdout] test tests::vector_tests::divide_vector_divides_each_value ... ok
[INFO] [stdout] test tests::vector_tests::negating_vector_inverts_each_value ... ok
[INFO] [stdout] test tests::vector_tests::subtract_vector_subtracts_second_value_from_for_for_each_pair ... ok
[INFO] [stdout] test tests::vector_tests::multiply_vector_multiplies_each_value ... ok
[INFO] [stdout] test tests::vector_tests::equalish_is_true_for_diff_below_epsilon ... ok
[INFO] [stdout] test tests::vector_tests::dot_products_of_vectors ... ok
[INFO] [stdout] test tests::vector_tests::magnitudes_of_vectors ... ok
[INFO] [stdout] test tests::vector_tests::normalizing_vectors ... ok
[INFO] [stdout] test tests::vector_tests::equalish_is_false_for_diff_above_epsilon ... ok
[INFO] [stdout] 
[INFO] [stderr]      Running unittests src/main.rs (/opt/rustwide/target/debug/deps/ray_tracer_challenge-895bff523afb1765)
[INFO] [stdout] test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] running 0 tests
[INFO] [stdout] 
[INFO] [stdout] test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
[INFO] [stdout] 
[INFO] [stderr]      Running tests/cucumber.rs (/opt/rustwide/target/debug/deps/cucumber-0431a0464bde3460)
[INFO] [stdout] [Cucumber v0.6.7]
[INFO] [stdout] 
[INFO] [stdout] Feature: Camera                                     features/camera.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Constructing a camera                   features/camera.feature:3:11
[INFO] [stdout]   ✔ Given hsize ← 160                               features/camera.feature:4:3
[INFO] [stdout]   ✔ And vsize ← 120                                 features/camera.feature:5:5
[INFO] [stdout]   ✔ And field_of_view ← π/2                         features/camera.feature:6:5
[INFO] [stdout]   ✔ When c ← camera(hsize, vsize, field_of_view)    features/camera.feature:7:3
[INFO] [stdout]   ✔ Then c.hsize = 160                              features/camera.feature:8:3
[INFO] [stdout]   ✔ And c.vsize = 120                               features/camera.feature:9:5
[INFO] [stdout]   ✔ And c.field_of_view = π/2                      features/camera.feature:10:5
[INFO] [stdout]   ✔ And c.transform = identity_matrix              features/camera.feature:11:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The pixel size for a horizontal canvas features/camera.feature:13:11
[INFO] [stdout]   ✔ Given c ← camera(200, 125, π/2)                features/camera.feature:14:3
[INFO] [stdout]   ✔ Then c.pixel_size = 0.01                       features/camera.feature:15:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The pixel size for a vertical canvas   features/camera.feature:17:11
[INFO] [stdout]   ✔ Given c ← camera(125, 200, π/2)                features/camera.feature:18:3
[INFO] [stdout]   ✔ Then c.pixel_size = 0.01                       features/camera.feature:19:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Constructing a ray through the center of the canvas
[INFO] [stdout]                                                   features/camera.feature:21:11
[INFO] [stdout]   ✔ Given c ← camera(201, 101, π/2)                features/camera.feature:22:3
[INFO] [stdout]   ✔ When r ← ray_for_pixel(c, 100, 50)             features/camera.feature:23:3
[INFO] [stdout]   ✔ Then r.origin = point(0, 0, 0)                 features/camera.feature:24:3
[INFO] [stdout]   ✔ And r.direction = vector(0, 0, -1)             features/camera.feature:25:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Constructing a ray through a corner of the canvas
[INFO] [stdout]                                                   features/camera.feature:27:11
[INFO] [stdout]   ✔ Given c ← camera(201, 101, π/2)                features/camera.feature:28:3
[INFO] [stdout]   ✔ When r ← ray_for_pixel(c, 0, 0)                features/camera.feature:29:3
[INFO] [stdout]   ✔ Then r.origin = point(0, 0, 0)                 features/camera.feature:30:3
[INFO] [stdout]   ✔ And r.direction = vector(0.66519, 0.33259, -0.66851)
[INFO] [stdout]                                                    features/camera.feature:31:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Constructing a ray when the camera is transformed
[INFO] [stdout]                                                   features/camera.feature:33:11
[INFO] [stdout]   ✔ Given c ← camera(201, 101, π/2)                features/camera.feature:34:3
[INFO] [stdout]   ✔ When c.transform ← rotation_y(π/4) * translation(0, -2, 5)
[INFO] [stdout]                                                    features/camera.feature:35:3
[INFO] [stdout]   ✔ And r ← ray_for_pixel(c, 100, 50)              features/camera.feature:36:5
[INFO] [stdout]   ✔ Then r.origin = point(0, 2, -5)                features/camera.feature:37:3
[INFO] [stdout]   ✔ And r.direction = vector(√2/2, 0, -√2/2)       features/camera.feature:38:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Rendering a world with a camera        features/camera.feature:40:11
[INFO] [stdout]   ✔ Given w ← default_world()                      features/camera.feature:41:3
[INFO] [stdout]   ✔ And c ← camera(11, 11, π/2)                    features/camera.feature:42:5
[INFO] [stdout]   ✔ And from ← point(0, 0, -5)                     features/camera.feature:43:5
[INFO] [stdout]   ✔ And to ← point(0, 0, 0)                        features/camera.feature:44:5
[INFO] [stdout]   ✔ And up ← vector(0, 1, 0)                       features/camera.feature:45:5
[INFO] [stdout]   ✔ And c.transform ← view_transform(from, to, up) features/camera.feature:46:5
[INFO] [stdout]   ✔ When image ← render(c, w)                      features/camera.feature:47:3
[INFO] [stdout]   ✔ Then pixel_at(image, 5, 5) = color(0.38066, 0.47583, 0.2855)
[INFO] [stdout]                                                    features/camera.feature:48:3
[INFO] [stdout] 
[INFO] [stdout] Feature: Colors                                     features/colors.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Adding colors                           features/colors.feature:3:13
[INFO] [stdout]   ✔ Given c1 = color(0.9, 0.6, 0.75)                features/colors.feature:4:5
[INFO] [stdout]   ✔ And c2 = color(0.7, 0.1, 0.25)                  features/colors.feature:5:5
[INFO] [stdout]   ✔ Then c1 + c2 = color(1.6, 0.7, 1.0)             features/colors.feature:6:5
[INFO] [stdout] 
[INFO] [stdout] Feature: Intersections                       features/intersections.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: An intersection encapsulates t and object
[INFO] [stdout]                                             features/intersections.feature:3:11
[INFO] [stdout]   ✔ Given s ← sphere()                       features/intersections.feature:4:3
[INFO] [stdout]   ✔ When i ← intersection(3.5, s)            features/intersections.feature:5:3
[INFO] [stdout]   ✔ Then i.t = 3.5                           features/intersections.feature:6:3
[INFO] [stdout]   ✔ And i.object = s                         features/intersections.feature:7:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Precomputing the state of an intersection
[INFO] [stdout]                                             features/intersections.feature:9:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1))
[INFO] [stdout]                                             features/intersections.feature:10:3
[INFO] [stdout]   ✔ And shape ← sphere()                    features/intersections.feature:11:5
[INFO] [stdout]   ✔ And i ← intersection(4, shape)          features/intersections.feature:12:5
[INFO] [stdout]   ✔ When comps ← prepare_computations(i, r) features/intersections.feature:13:3
[INFO] [stdout]   ✔ Then comps.t = i.t                      features/intersections.feature:14:3
[INFO] [stdout]   ✔ And comps.object = i.object             features/intersections.feature:15:5
[INFO] [stdout]   ✔ And comps.point = point(0, 0, -1)       features/intersections.feature:16:5
[INFO] [stdout]   ✔ And comps.eyev = vector(0, 0, -1)       features/intersections.feature:17:5
[INFO] [stdout]   ✔ And comps.normalv = vector(0, 0, -1)    features/intersections.feature:18:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The hit, when an intersection occurs on the outside
[INFO] [stdout]                                            features/intersections.feature:27:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1))
[INFO] [stdout]                                             features/intersections.feature:28:3
[INFO] [stdout]   ✔ And shape ← sphere()                    features/intersections.feature:29:5
[INFO] [stdout]   ✔ And i ← intersection(4, shape)          features/intersections.feature:30:5
[INFO] [stdout]   ✔ When comps ← prepare_computations(i, r) features/intersections.feature:31:3
[INFO] [stdout]   ✔ Then comps.inside = false               features/intersections.feature:32:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The hit, when an intersection occurs on the inside
[INFO] [stdout]                                            features/intersections.feature:34:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, 0), vector(0, 0, 1))
[INFO] [stdout]                                             features/intersections.feature:35:3
[INFO] [stdout]   ✔ And shape ← sphere()                    features/intersections.feature:36:5
[INFO] [stdout]   ✔ And i ← intersection(1, shape)          features/intersections.feature:37:5
[INFO] [stdout]   ✔ When comps ← prepare_computations(i, r) features/intersections.feature:38:3
[INFO] [stdout]   ✔ Then comps.point = point(0, 0, 1)       features/intersections.feature:39:3
[INFO] [stdout]   ✔ And comps.eyev = vector(0, 0, -1)       features/intersections.feature:40:5
[INFO] [stdout]   ✔ And comps.inside = true                 features/intersections.feature:41:5
[INFO] [stdout]   ✔ And comps.normalv = vector(0, 0, -1)    features/intersections.feature:43:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The hit should offset the point features/intersections.feature:45:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1))
[INFO] [stdout]                                             features/intersections.feature:46:3
[INFO] [stdout]   ✔ And shape ← sphere() with:              features/intersections.feature:47:5
[INFO] [stdout]       |           |                      |
[INFO] [stdout]       | transform | translation(0, 0, 1) |
[INFO] [stdout]   ✔ And i ← intersection(5, shape)          features/intersections.feature:49:5
[INFO] [stdout]   ✔ When comps ← prepare_computations(i, r) features/intersections.feature:50:3
[INFO] [stdout]   ✔ Then comps.over_point.z < -EPSILON/2    features/intersections.feature:51:3
[INFO] [stdout]   ✔ And comps.point.z > comps.over_point.z  features/intersections.feature:52:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Aggregating intersections       features/intersections.feature:64:11
[INFO] [stdout]   ✔ Given s ← sphere()                      features/intersections.feature:65:3
[INFO] [stdout]   ✔ And i1 ← intersection(1, s)             features/intersections.feature:66:5
[INFO] [stdout]   ✔ And i2 ← intersection(2, s)             features/intersections.feature:67:5
[INFO] [stdout]   ✔ When xs ← intersections(i1, i2)         features/intersections.feature:68:3
[INFO] [stdout]   ✔ Then xs.count = 2                       features/intersections.feature:69:3
[INFO] [stdout]   ✔ And xs[0].t = 1                         features/intersections.feature:70:5
[INFO] [stdout]   ✔ And xs[1].t = 2                         features/intersections.feature:71:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The hit, when all intersections have positive t
[INFO] [stdout]                                            features/intersections.feature:73:11
[INFO] [stdout]   ✔ Given s ← sphere()                      features/intersections.feature:74:3
[INFO] [stdout]   ✔ And i1 ← intersection(1, s)             features/intersections.feature:75:5
[INFO] [stdout]   ✔ And i2 ← intersection(2, s)             features/intersections.feature:76:5
[INFO] [stdout]   ✔ And xs ← intersections(i2, i1)          features/intersections.feature:77:5
[INFO] [stdout]   ✔ When i ← hit(xs)                        features/intersections.feature:78:3
[INFO] [stdout]   ✔ Then i = i1                             features/intersections.feature:79:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The hit, when some intersections have negative t
[INFO] [stdout]                                            features/intersections.feature:81:11
[INFO] [stdout]   ✔ Given s ← sphere()                      features/intersections.feature:82:3
[INFO] [stdout]   ✔ And i1 ← intersection(-1, s)            features/intersections.feature:83:5
[INFO] [stdout]   ✔ And i2 ← intersection(1, s)             features/intersections.feature:84:5
[INFO] [stdout]   ✔ And xs ← intersections(i2, i1)          features/intersections.feature:85:5
[INFO] [stdout]   ✔ When i ← hit(xs)                        features/intersections.feature:86:3
[INFO] [stdout]   ✔ Then i = i2                             features/intersections.feature:87:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The hit, when all intersections have negative t
[INFO] [stdout]                                            features/intersections.feature:89:11
[INFO] [stdout]   ✔ Given s ← sphere()                      features/intersections.feature:90:3
[INFO] [stdout]   ✔ And i1 ← intersection(-2, s)            features/intersections.feature:91:5
[INFO] [stdout]   ✔ And i2 ← intersection(-1, s)            features/intersections.feature:92:5
[INFO] [stdout]   ✔ And xs ← intersections(i2, i1)          features/intersections.feature:93:5
[INFO] [stdout]   ✔ When i ← hit(xs)                        features/intersections.feature:94:3
[INFO] [stdout]   ✔ Then i is nothing                       features/intersections.feature:95:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The hit is always the lowest nonnegative intersection
[INFO] [stdout]                                            features/intersections.feature:97:11
[INFO] [stdout]   ✔ Given s ← sphere()                      features/intersections.feature:98:3
[INFO] [stdout]   ✔ And i1 ← intersection(5, s)             features/intersections.feature:99:3
[INFO] [stdout]   ✔ And i2 ← intersection(7, s)            features/intersections.feature:100:3
[INFO] [stdout]   ✔ And i3 ← intersection(-3, s)           features/intersections.feature:101:3
[INFO] [stdout]   ✔ And i4 ← intersection(2, s)            features/intersections.feature:102:3
[INFO] [stdout]   ✔ And xs ← intersections(i1, i2, i3, i4) features/intersections.feature:103:3
[INFO] [stdout]   ✔ When i ← hit(xs)                       features/intersections.feature:104:1
[INFO] [stdout]   ✔ Then i = i4                            features/intersections.feature:105:1
[INFO] [stdout] 
[INFO] [stdout] Feature: Lights                                     features/lights.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A point light has a position and intensity
[INFO] [stdout]                                                    features/lights.feature:3:11
[INFO] [stdout]   ✔ Given intensity ← color(1, 1, 1)                features/lights.feature:4:3
[INFO] [stdout]   ✔ And position ← point(0, 0, 0)                   features/lights.feature:5:5
[INFO] [stdout]   ✔ When light ← point_light(position, intensity)   features/lights.feature:6:3
[INFO] [stdout]   ✔ Then light.position = position                  features/lights.feature:7:3
[INFO] [stdout]   ✔ And light.intensity = intensity                 features/lights.feature:8:5
[INFO] [stdout] 
[INFO] [stdout] Feature: Materials                               features/materials.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The default material                 features/materials.feature:7:11
[INFO] [stdout]   ✔ Given m ← material()                         features/materials.feature:4:3
[INFO] [stdout]   ✔ And position ← point(0, 0, 0)                features/materials.feature:5:5
[INFO] [stdout]   ✔ Given m ← material()                         features/materials.feature:8:3
[INFO] [stdout]   ✔ Then m.color = color(1, 1, 1)                features/materials.feature:9:3
[INFO] [stdout]   ✔ And m.ambient = 0.1                         features/materials.feature:10:5
[INFO] [stdout]   ✔ And m.diffuse = 0.9                         features/materials.feature:11:5
[INFO] [stdout]   ✔ And m.specular = 0.9                        features/materials.feature:12:5
[INFO] [stdout]   ✔ And m.shininess = 200.0                     features/materials.feature:13:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Lighting with the eye between the light and the surface
[INFO] [stdout]                                                features/materials.feature:24:11
[INFO] [stdout]   ✔ Given m ← material()                         features/materials.feature:4:3
[INFO] [stdout]   ✔ And position ← point(0, 0, 0)                features/materials.feature:5:5
[INFO] [stdout]   ✔ Given eyev ← vector(0, 0, -1)               features/materials.feature:25:3
[INFO] [stdout]   ✔ And normalv ← vector(0, 0, -1)              features/materials.feature:26:5
[INFO] [stdout]   ✔ And light ← point_light(point(0, 0, -10), color(1, 1, 1))
[INFO] [stdout]                                                 features/materials.feature:27:5
[INFO] [stdout]   ✔ When result ← lighting(m, light, position, eyev, normalv)
[INFO] [stdout]                                                 features/materials.feature:28:3
[INFO] [stdout]   ✔ Then result = color(1.9, 1.9, 1.9)          features/materials.feature:29:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Lighting with the eye between light and surface, eye offset 45°
[INFO] [stdout]                                                features/materials.feature:31:11
[INFO] [stdout]   ✔ Given m ← material()                         features/materials.feature:4:3
[INFO] [stdout]   ✔ And position ← point(0, 0, 0)                features/materials.feature:5:5
[INFO] [stdout]   ✔ Given eyev ← vector(0, √2/2, -√2/2)         features/materials.feature:32:3
[INFO] [stdout]   ✔ And normalv ← vector(0, 0, -1)              features/materials.feature:33:5
[INFO] [stdout]   ✔ And light ← point_light(point(0, 0, -10), color(1, 1, 1))
[INFO] [stdout]                                                 features/materials.feature:34:5
[INFO] [stdout]   ✔ When result ← lighting(m, light, position, eyev, normalv)
[INFO] [stdout]                                                 features/materials.feature:35:3
[INFO] [stdout]   ✔ Then result = color(1.0, 1.0, 1.0)          features/materials.feature:36:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Lighting with eye opposite surface, light offset 45°
[INFO] [stdout]                                                features/materials.feature:38:11
[INFO] [stdout]   ✔ Given m ← material()                         features/materials.feature:4:3
[INFO] [stdout]   ✔ And position ← point(0, 0, 0)                features/materials.feature:5:5
[INFO] [stdout]   ✔ Given eyev ← vector(0, 0, -1)               features/materials.feature:39:3
[INFO] [stdout]   ✔ And normalv ← vector(0, 0, -1)              features/materials.feature:40:5
[INFO] [stdout]   ✔ And light ← point_light(point(0, 10, -10), color(1, 1, 1))
[INFO] [stdout]                                                 features/materials.feature:41:5
[INFO] [stdout]   ✔ When result ← lighting(m, light, position, eyev, normalv)
[INFO] [stdout]                                                 features/materials.feature:42:3
[INFO] [stdout]   ✔ Then result = color(0.7364, 0.7364, 0.7364) features/materials.feature:43:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Lighting with eye in the path of the reflection vector
[INFO] [stdout]                                                features/materials.feature:45:11
[INFO] [stdout]   ✔ Given m ← material()                         features/materials.feature:4:3
[INFO] [stdout]   ✔ And position ← point(0, 0, 0)                features/materials.feature:5:5
[INFO] [stdout]   ✔ Given eyev ← vector(0, -√2/2, -√2/2)        features/materials.feature:46:3
[INFO] [stdout]   ✔ And normalv ← vector(0, 0, -1)              features/materials.feature:47:5
[INFO] [stdout]   ✔ And light ← point_light(point(0, 10, -10), color(1, 1, 1))
[INFO] [stdout]                                                 features/materials.feature:48:5
[INFO] [stdout]   ✔ When result ← lighting(m, light, position, eyev, normalv)
[INFO] [stdout]                                                 features/materials.feature:49:3
[INFO] [stdout]   ✔ Then result = color(1.63639, 1.63639, 1.63639)
[INFO] [stdout]                                                 features/materials.feature:50:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Lighting with the light behind the surface
[INFO] [stdout]                                                features/materials.feature:52:11
[INFO] [stdout]   ✔ Given m ← material()                         features/materials.feature:4:3
[INFO] [stdout]   ✔ And position ← point(0, 0, 0)                features/materials.feature:5:5
[INFO] [stdout]   ✔ Given eyev ← vector(0, 0, -1)               features/materials.feature:53:3
[INFO] [stdout]   ✔ And normalv ← vector(0, 0, -1)              features/materials.feature:54:5
[INFO] [stdout]   ✔ And light ← point_light(point(0, 0, 10), color(1, 1, 1))
[INFO] [stdout]                                                 features/materials.feature:55:5
[INFO] [stdout]   ✔ When result ← lighting(m, light, position, eyev, normalv)
[INFO] [stdout]                                                 features/materials.feature:56:3
[INFO] [stdout]   ✔ Then result = color(0.1, 0.1, 0.1)          features/materials.feature:57:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Lighting with the surface in shadow features/materials.feature:59:11
[INFO] [stdout]   ✔ Given m ← material()                         features/materials.feature:4:3
[INFO] [stdout]   ✔ And position ← point(0, 0, 0)                features/materials.feature:5:5
[INFO] [stdout]   ✔ Given eyev ← vector(0, 0, -1)               features/materials.feature:60:3
[INFO] [stdout]   ✔ And normalv ← vector(0, 0, -1)              features/materials.feature:61:5
[INFO] [stdout]   ✔ And light ← point_light(point(0, 0, -10), color(1, 1, 1))
[INFO] [stdout]                                                 features/materials.feature:62:5
[INFO] [stdout]   ✔ And in_shadow ← true                        features/materials.feature:63:5
[INFO] [stdout]   ✔ When result ← lighting(m, light, position, eyev, normalv, in_shadow)
[INFO] [stdout]                                                 features/materials.feature:64:3
[INFO] [stdout]   ✔ Then result = color(0.1, 0.1, 0.1)          features/materials.feature:65:3
[INFO] [stdout] 
[INFO] [stdout] Feature: Matrices                                 features/matrices.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Constructing and inspecting a 4x4 matrix
[INFO] [stdout]                                                  features/matrices.feature:3:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix M:             features/matrices.feature:4:3
[INFO] [stdout]       | 1    | 2    | 3    | 4    |
[INFO] [stdout]       |  5.5 |  6.5 |  7.5 |  8.5 |
[INFO] [stdout]       |    9 |   10 |   11 |   12 |
[INFO] [stdout]       | 13.5 | 14.5 | 15.5 | 16.5 |
[INFO] [stdout]   ✔ Then M[0,0] = 1                               features/matrices.feature:9:3
[INFO] [stdout]   ✔ And M[0,3] = 4                               features/matrices.feature:10:5
[INFO] [stdout]   ✔ And M[1,0] = 5.5                             features/matrices.feature:11:5
[INFO] [stdout]   ✔ And M[1,2] = 7.5                             features/matrices.feature:12:5
[INFO] [stdout]   ✔ And M[2,2] = 11                              features/matrices.feature:13:5
[INFO] [stdout]   ✔ And M[3,0] = 13.5                            features/matrices.feature:14:5
[INFO] [stdout]   ✔ And M[3,2] = 15.5                            features/matrices.feature:15:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A 2x2 matrix ought to be representable
[INFO] [stdout]                                                 features/matrices.feature:17:11
[INFO] [stdout]   ✔ Given the following 2x2 matrix M:            features/matrices.feature:18:3
[INFO] [stdout]       | -3 | 5  |
[INFO] [stdout]       |  1 | -2 |
[INFO] [stdout]   ✔ Then M[0,0] = -3                             features/matrices.feature:21:3
[INFO] [stdout]   ✔ And M[0,1] = 5                               features/matrices.feature:22:5
[INFO] [stdout]   ✔ And M[1,0] = 1                               features/matrices.feature:23:5
[INFO] [stdout]   ✔ And M[1,1] = -2                              features/matrices.feature:24:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A 3x3 matrix ought to be representable
[INFO] [stdout]                                                 features/matrices.feature:26:11
[INFO] [stdout]   ✔ Given the following 3x3 matrix M:            features/matrices.feature:27:3
[INFO] [stdout]       | -3 | 5  | 0  |
[INFO] [stdout]       |  1 | -2 | -7 |
[INFO] [stdout]       |  0 |  1 |  1 |
[INFO] [stdout]   ✔ Then M[0,0] = -3                             features/matrices.feature:31:3
[INFO] [stdout]   ✔ And M[1,1] = -2                              features/matrices.feature:32:5
[INFO] [stdout]   ✔ And M[2,2] = 1                               features/matrices.feature:33:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Matrix equality with identical matrices
[INFO] [stdout]                                                 features/matrices.feature:35:11
[INFO] [stdout]   ✔ Given the following matrix A:                features/matrices.feature:36:3
[INFO] [stdout]       | 1 | 2 | 3 | 4 |
[INFO] [stdout]       | 5 | 6 | 7 | 8 |
[INFO] [stdout]       | 9 | 8 | 7 | 6 |
[INFO] [stdout]       | 5 | 4 | 3 | 2 |
[INFO] [stdout]   ✔ And the following matrix B:                  features/matrices.feature:41:5
[INFO] [stdout]       | 1 | 2 | 3 | 4 |
[INFO] [stdout]       | 5 | 6 | 7 | 8 |
[INFO] [stdout]       | 9 | 8 | 7 | 6 |
[INFO] [stdout]       | 5 | 4 | 3 | 2 |
[INFO] [stdout]   ✔ Then A = B                                   features/matrices.feature:46:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Matrix equality with different matrices
[INFO] [stdout]                                                 features/matrices.feature:48:11
[INFO] [stdout]   ✔ Given the following matrix A:                features/matrices.feature:49:3
[INFO] [stdout]       | 1 | 2 | 3 | 4 |
[INFO] [stdout]       | 5 | 6 | 7 | 8 |
[INFO] [stdout]       | 9 | 8 | 7 | 6 |
[INFO] [stdout]       | 5 | 4 | 3 | 2 |
[INFO] [stdout]   ✔ And the following matrix B:                  features/matrices.feature:54:5
[INFO] [stdout]       | 2 | 3 | 4 | 5 |
[INFO] [stdout]       | 6 | 7 | 8 | 9 |
[INFO] [stdout]       | 8 | 7 | 6 | 5 |
[INFO] [stdout]       | 4 | 3 | 2 | 1 |
[INFO] [stdout]   ✔ Then A != B                                  features/matrices.feature:59:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Multiplying two matrices             features/matrices.feature:61:11
[INFO] [stdout]   ✔ Given the following matrix A:                features/matrices.feature:62:3
[INFO] [stdout]       | 1 | 2 | 3 | 4 |
[INFO] [stdout]       | 5 | 6 | 7 | 8 |
[INFO] [stdout]       | 9 | 8 | 7 | 6 |
[INFO] [stdout]       | 5 | 4 | 3 | 2 |
[INFO] [stdout]   ✔ And the following matrix B:                  features/matrices.feature:67:5
[INFO] [stdout]       | -2 | 1 | 2 | 3  |
[INFO] [stdout]       |  3 | 2 | 1 | -1 |
[INFO] [stdout]       |  4 | 3 | 6 |  5 |
[INFO] [stdout]       |  1 | 2 | 7 |  8 |
[INFO] [stdout]   ✔ Then A * B is the following 4x4 matrix:      features/matrices.feature:72:3
[INFO] [stdout]       | 20 | 22 | 50  | 48  |
[INFO] [stdout]       | 44 | 54 | 114 | 108 |
[INFO] [stdout]       | 40 | 58 | 110 | 102 |
[INFO] [stdout]       | 16 | 26 |  46 |  42 |
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A matrix multiplied by a tuple       features/matrices.feature:78:11
[INFO] [stdout]   ✔ Given the following matrix A:                features/matrices.feature:79:3
[INFO] [stdout]       | 1 | 2 | 3 | 4 |
[INFO] [stdout]       | 2 | 4 | 4 | 2 |
[INFO] [stdout]       | 8 | 6 | 4 | 1 |
[INFO] [stdout]       | 0 | 0 | 0 | 1 |
[INFO] [stdout]   ✔ And b ← tuple(1, 2, 3, 1)                    features/matrices.feature:84:5
[INFO] [stdout]   ✔ Then A * b = tuple(18, 24, 33, 1)            features/matrices.feature:85:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Multiplying a matrix by the identity matrix
[INFO] [stdout]                                                 features/matrices.feature:87:11
[INFO] [stdout]   ✔ Given the following matrix A:                features/matrices.feature:88:3
[INFO] [stdout]       | 0 | 1 | 2  | 4  |
[INFO] [stdout]       | 1 | 2 |  4 |  8 |
[INFO] [stdout]       | 2 | 4 |  8 | 16 |
[INFO] [stdout]       | 4 | 8 | 16 | 32 |
[INFO] [stdout]   ✔ Then A * identity_matrix = A                 features/matrices.feature:93:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Transposing a matrix                 features/matrices.feature:99:11
[INFO] [stdout]   ✔ Given the following matrix A:               features/matrices.feature:100:3
[INFO] [stdout]       | 0 | 9 | 3 | 0 |
[INFO] [stdout]       | 9 | 8 | 0 | 8 |
[INFO] [stdout]       | 1 | 8 | 5 | 3 |
[INFO] [stdout]       | 0 | 0 | 5 | 8 |
[INFO] [stdout]   ✔ Then transpose(A) is the following matrix:  features/matrices.feature:105:3
[INFO] [stdout]       | 0 | 9 | 1 | 0 |
[INFO] [stdout]       | 9 | 8 | 8 | 0 |
[INFO] [stdout]       | 3 | 0 | 5 | 5 |
[INFO] [stdout]       | 0 | 8 | 3 | 8 |
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Calculating the determinant of a 2x2 matrix
[INFO] [stdout]                                                features/matrices.feature:115:11
[INFO] [stdout]   ✔ Given the following 2x2 matrix A:           features/matrices.feature:116:3
[INFO] [stdout]       | 1  | 5 |
[INFO] [stdout]       | -3 | 2 |
[INFO] [stdout]   ✔ Then determinant(A) = 17                    features/matrices.feature:119:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A submatrix of a 3x3 matrix is a 2x2 matrix
[INFO] [stdout]                                                features/matrices.feature:121:11
[INFO] [stdout]   ✔ Given the following 3x3 matrix A:           features/matrices.feature:122:3
[INFO] [stdout]       | 1  | 5 | 0  |
[INFO] [stdout]       | -3 | 2 |  7 |
[INFO] [stdout]       |  0 | 6 | -3 |
[INFO] [stdout]   ✔ Then submatrix(A, 0, 2) is the following 2x2 matrix:
[INFO] [stdout]                                                 features/matrices.feature:126:3
[INFO] [stdout]       | -3 | 2 |
[INFO] [stdout]       |  0 | 6 |
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A submatrix of a 4x4 matrix is a 3x3 matrix
[INFO] [stdout]                                                features/matrices.feature:130:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix A:           features/matrices.feature:131:3
[INFO] [stdout]       | -6 | 1 | 1  | 6 |
[INFO] [stdout]       | -8 | 5 |  8 | 6 |
[INFO] [stdout]       | -1 | 0 |  8 | 2 |
[INFO] [stdout]       | -7 | 1 | -1 | 1 |
[INFO] [stdout]   ✔ Then submatrix(A, 2, 1) is the following 3x3 matrix:
[INFO] [stdout]                                                 features/matrices.feature:136:3
[INFO] [stdout]       | -6 | 1  | 6 |
[INFO] [stdout]       | -8 |  8 | 6 |
[INFO] [stdout]       | -7 | -1 | 1 |
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Calculating a minor of a 3x3 matrix features/matrices.feature:141:11
[INFO] [stdout]   ✔ Given the following 3x3 matrix A:           features/matrices.feature:142:3
[INFO] [stdout]       | 3 | 5  | 0  |
[INFO] [stdout]       | 2 | -1 | -7 |
[INFO] [stdout]       | 6 | -1 |  5 |
[INFO] [stdout]   ✔ And B ← submatrix(A, 1, 0)                  features/matrices.feature:146:5
[INFO] [stdout]   ✔ Then determinant(B) = 25                    features/matrices.feature:147:3
[INFO] [stdout]   ✔ And minor(A, 1, 0) = 25                     features/matrices.feature:148:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Calculating a cofactor of a 3x3 matrix
[INFO] [stdout]                                                features/matrices.feature:150:11
[INFO] [stdout]   ✔ Given the following 3x3 matrix A:           features/matrices.feature:151:3
[INFO] [stdout]       | 3 | 5  | 0  |
[INFO] [stdout]       | 2 | -1 | -7 |
[INFO] [stdout]       | 6 | -1 |  5 |
[INFO] [stdout]   ✔ Then minor(A, 0, 0) = -12                   features/matrices.feature:155:3
[INFO] [stdout]   ✔ And cofactor(A, 0, 0) = -12                 features/matrices.feature:156:5
[INFO] [stdout]   ✔ And minor(A, 1, 0) = 25                     features/matrices.feature:157:5
[INFO] [stdout]   ✔ And cofactor(A, 1, 0) = -25                 features/matrices.feature:158:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Calculating the determinant of a 3x3 matrix
[INFO] [stdout]                                                features/matrices.feature:160:11
[INFO] [stdout]   ✔ Given the following 3x3 matrix A:           features/matrices.feature:161:3
[INFO] [stdout]       | 1  | 2 | 6  |
[INFO] [stdout]       | -5 | 8 | -4 |
[INFO] [stdout]       |  2 | 6 |  4 |
[INFO] [stdout]   ✔ Then cofactor(A, 0, 0) = 56                 features/matrices.feature:165:3
[INFO] [stdout]   ✔ And cofactor(A, 0, 1) = 12                  features/matrices.feature:166:5
[INFO] [stdout]   ✔ And cofactor(A, 0, 2) = -46                 features/matrices.feature:167:5
[INFO] [stdout]   ✔ And determinant(A) = -196                   features/matrices.feature:168:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Calculating the determinant of a 4x4 matrix
[INFO] [stdout]                                                features/matrices.feature:170:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix A:           features/matrices.feature:171:3
[INFO] [stdout]       | -2 | -8 | 3  | 5  |
[INFO] [stdout]       | -3 |  1 |  7 |  3 |
[INFO] [stdout]       |  1 |  2 | -9 |  6 |
[INFO] [stdout]       | -6 |  7 |  7 | -9 |
[INFO] [stdout]   ✔ Then cofactor(A, 0, 0) = 690                features/matrices.feature:176:3
[INFO] [stdout]   ✔ And cofactor(A, 0, 1) = 447                 features/matrices.feature:177:5
[INFO] [stdout]   ✔ And cofactor(A, 0, 2) = 210                 features/matrices.feature:178:5
[INFO] [stdout]   ✔ And cofactor(A, 0, 3) = 51                  features/matrices.feature:179:5
[INFO] [stdout]   ✔ And determinant(A) = -4071                  features/matrices.feature:180:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Testing an invertible matrix for invertibility
[INFO] [stdout]                                                features/matrices.feature:182:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix A:           features/matrices.feature:183:3
[INFO] [stdout]       | 6 | 4  | 4 | 4  |
[INFO] [stdout]       | 5 |  5 | 7 |  6 |
[INFO] [stdout]       | 4 | -9 | 3 | -7 |
[INFO] [stdout]       | 9 |  1 | 7 | -6 |
[INFO] [stdout]   ✔ Then determinant(A) = -2120                 features/matrices.feature:188:3
[INFO] [stdout]   ✔ And A is invertible                         features/matrices.feature:189:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Testing a noninvertible matrix for invertibility
[INFO] [stdout]                                                features/matrices.feature:191:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix A:           features/matrices.feature:192:3
[INFO] [stdout]       | -4 | 2  | -2 | -3 |
[INFO] [stdout]       |  9 |  6 |  2 |  6 |
[INFO] [stdout]       |  0 | -5 |  1 | -5 |
[INFO] [stdout]       |  0 |  0 |  0 |  0 |
[INFO] [stdout]   ✔ Then determinant(A) = 0                     features/matrices.feature:197:3
[INFO] [stdout]   ✔ And A is not invertible                     features/matrices.feature:198:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Calculating the inverse of a matrix features/matrices.feature:200:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix A:           features/matrices.feature:201:3
[INFO] [stdout]       | -5 | 2  | 6  | -8 |
[INFO] [stdout]       |  1 | -5 |  1 |  8 |
[INFO] [stdout]       |  7 |  7 | -6 | -7 |
[INFO] [stdout]       |  1 | -3 |  7 |  4 |
[INFO] [stdout]   ✔ And B ← inverse(A)                          features/matrices.feature:206:5
[INFO] [stdout]   ✔ Then determinant(A) = 532                   features/matrices.feature:207:3
[INFO] [stdout]   ✔ And cofactor(A, 2, 3) = -160                features/matrices.feature:208:5
[INFO] [stdout]   ✔ And B[3,2] = -160/532                       features/matrices.feature:209:5
[INFO] [stdout]   ✔ And cofactor(A, 3, 2) = 105                 features/matrices.feature:210:5
[INFO] [stdout]   ✔ And B[2,3] = 105/532                        features/matrices.feature:211:5
[INFO] [stdout]   ✔ And B is the following 4x4 matrix:          features/matrices.feature:212:5
[INFO] [stdout]       | 0.21805  | 0.45113  | 0.24060  | -0.04511 |
[INFO] [stdout]       | -0.80827 | -1.45677 | -0.44361 |  0.52068 |
[INFO] [stdout]       | -0.07895 | -0.22368 | -0.05263 |  0.19737 |
[INFO] [stdout]       | -0.52256 | -0.81391 | -0.30075 |  0.30639 |
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Calculating the inverse of another matrix
[INFO] [stdout]                                                features/matrices.feature:218:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix A:           features/matrices.feature:219:3
[INFO] [stdout]       | 8  | -5 | 9  | 2  |
[INFO] [stdout]       |  7 |  5 |  6 |  1 |
[INFO] [stdout]       | -6 |  0 |  9 |  6 |
[INFO] [stdout]       | -3 |  0 | -9 | -4 |
[INFO] [stdout]   ✔ Then inverse(A) is the following 4x4 matrix:
[INFO] [stdout]                                                 features/matrices.feature:224:3
[INFO] [stdout]       | -0.15385 | -0.15385 | -0.28205 | -0.53846 |
[INFO] [stdout]       | -0.07692 |  0.12308 |  0.02564 |  0.03077 |
[INFO] [stdout]       |  0.35897 |  0.35897 |  0.43590 |  0.92308 |
[INFO] [stdout]       | -0.69231 | -0.69231 | -0.76923 | -1.92308 |
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Calculating the inverse of a third matrix
[INFO] [stdout]                                                features/matrices.feature:230:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix A:           features/matrices.feature:231:3
[INFO] [stdout]       | 9  | 3  | 0  | 9  |
[INFO] [stdout]       | -5 | -2 | -6 | -3 |
[INFO] [stdout]       | -4 |  9 |  6 |  4 |
[INFO] [stdout]       | -7 |  6 |  6 |  2 |
[INFO] [stdout]   ✔ Then inverse(A) is the following 4x4 matrix:
[INFO] [stdout]                                                 features/matrices.feature:236:3
[INFO] [stdout]       | -0.04074 | -0.07778 | 0.14444  | -0.22222 |
[INFO] [stdout]       | -0.07778 |  0.03333 |  0.36667 | -0.33333 |
[INFO] [stdout]       | -0.02901 | -0.14630 | -0.10926 |  0.12963 |
[INFO] [stdout]       |  0.17778 |  0.06667 | -0.26667 |  0.33333 |
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Multiplying a product by its inverse
[INFO] [stdout]                                                features/matrices.feature:242:11
[INFO] [stdout]   ✔ Given the following 4x4 matrix A:           features/matrices.feature:243:3
[INFO] [stdout]       | 3  | -9 | 7  | 3  |
[INFO] [stdout]       |  3 | -8 |  2 | -9 |
[INFO] [stdout]       | -4 |  4 |  4 |  1 |
[INFO] [stdout]       | -6 |  5 | -1 |  1 |
[INFO] [stdout]   ✔ And the following 4x4 matrix B:             features/matrices.feature:248:5
[INFO] [stdout]       | 8 | 2  | 2 | 2 |
[INFO] [stdout]       | 3 | -1 | 7 | 0 |
[INFO] [stdout]       | 7 |  0 | 5 | 4 |
[INFO] [stdout]       | 6 | -2 | 0 | 5 |
[INFO] [stdout]   ✔ And C ← A * B                               features/matrices.feature:253:5
[INFO] [stdout]   ✔ Then C * inverse(B) = A                     features/matrices.feature:254:3
[INFO] [stdout] 
[INFO] [stdout] Feature: Planes                                     features/planes.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The normal of a plane is constant everywhere
[INFO] [stdout]                                                    features/planes.feature:3:11
[INFO] [stdout]   ✔ Given p ← plane()                               features/planes.feature:4:3
[INFO] [stdout]   ✔ When n1 ← local_normal_at(p, point(0, 0, 0))    features/planes.feature:5:3
[INFO] [stdout]   ✔ And n2 ← local_normal_at(p, point(10, 0, -10))  features/planes.feature:6:5
[INFO] [stdout]   ✔ And n3 ← local_normal_at(p, point(-5, 0, 150))  features/planes.feature:7:5
[INFO] [stdout]   ✔ Then n1 = vector(0, 1, 0)                       features/planes.feature:8:3
[INFO] [stdout]   ✔ And n2 = vector(0, 1, 0)                        features/planes.feature:9:5
[INFO] [stdout]   ✔ And n3 = vector(0, 1, 0)                       features/planes.feature:10:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Intersect with a ray parallel to the plane
[INFO] [stdout]                                                   features/planes.feature:12:11
[INFO] [stdout]   ✔ Given p ← plane()                              features/planes.feature:13:3
[INFO] [stdout]   ✔ And r ← ray(point(0, 10, 0), vector(0, 0, 1))  features/planes.feature:14:5
[INFO] [stdout]   ✔ When xs ← local_intersect(p, r)                features/planes.feature:15:3
[INFO] [stdout]   ✔ Then xs is empty                               features/planes.feature:16:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Intersect with a coplanar ray          features/planes.feature:18:11
[INFO] [stdout]   ✔ Given p ← plane()                              features/planes.feature:19:3
[INFO] [stdout]   ✔ And r ← ray(point(0, 0, 0), vector(0, 0, 1))   features/planes.feature:20:5
[INFO] [stdout]   ✔ When xs ← local_intersect(p, r)                features/planes.feature:21:3
[INFO] [stdout]   ✔ Then xs is empty                               features/planes.feature:22:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A ray intersecting a plane from above  features/planes.feature:24:11
[INFO] [stdout]   ✔ Given p ← plane()                              features/planes.feature:25:3
[INFO] [stdout]   ✔ And r ← ray(point(0, 1, 0), vector(0, -1, 0))  features/planes.feature:26:5
[INFO] [stdout]   ✔ When xs ← local_intersect(p, r)                features/planes.feature:27:3
[INFO] [stdout]   ✔ Then xs.count = 1                              features/planes.feature:28:3
[INFO] [stdout]   ✔ And xs[0].t = 1                                features/planes.feature:29:5
[INFO] [stdout]   ✔ And xs[0].object = p                           features/planes.feature:30:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A ray intersecting a plane from below  features/planes.feature:32:11
[INFO] [stdout]   ✔ Given p ← plane()                              features/planes.feature:33:3
[INFO] [stdout]   ✔ And r ← ray(point(0, -1, 0), vector(0, 1, 0))  features/planes.feature:34:5
[INFO] [stdout]   ✔ When xs ← local_intersect(p, r)                features/planes.feature:35:3
[INFO] [stdout]   ✔ Then xs.count = 1                              features/planes.feature:36:3
[INFO] [stdout]   ✔ And xs[0].t = 1                                features/planes.feature:37:5
[INFO] [stdout]   ✔ And xs[0].object = p                           features/planes.feature:38:5
[INFO] [stdout] 
[INFO] [stdout] Feature: Rays                                         features/rays.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Creating and querying a ray               features/rays.feature:3:11
[INFO] [stdout]   ✔ Given origin ← point(1, 2, 3)                     features/rays.feature:4:3
[INFO] [stdout]   ✔ And direction ← vector(4, 5, 6)                   features/rays.feature:5:5
[INFO] [stdout]   ✔ When r ← ray(origin, direction)                   features/rays.feature:6:3
[INFO] [stdout]   ✔ Then r.origin = origin                            features/rays.feature:7:3
[INFO] [stdout]   ✔ And r.direction = direction                       features/rays.feature:8:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Computing a point from a distance        features/rays.feature:10:11
[INFO] [stdout]   ✔ Given r ← ray(point(2, 3, 4), vector(1, 0, 0))   features/rays.feature:11:3
[INFO] [stdout]   ✔ Then position(r, 0) = point(2, 3, 4)             features/rays.feature:12:3
[INFO] [stdout]   ✔ And position(r, 1) = point(3, 3, 4)              features/rays.feature:13:5
[INFO] [stdout]   ✔ And position(r, -1) = point(1, 3, 4)             features/rays.feature:14:5
[INFO] [stdout]   ✔ And position(r, 2.5) = point(4.5, 3, 4)          features/rays.feature:15:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Translating a ray                        features/rays.feature:17:11
[INFO] [stdout]   ✔ Given r ← ray(point(1, 2, 3), vector(0, 1, 0))   features/rays.feature:18:3
[INFO] [stdout]   ✔ And m ← translation(3, 4, 5)                     features/rays.feature:19:5
[INFO] [stdout]   ✔ When r2 ← transform(r, m)                        features/rays.feature:20:3
[INFO] [stdout]   ✔ Then r2.origin = point(4, 6, 8)                  features/rays.feature:21:3
[INFO] [stdout]   ✔ And r2.direction = vector(0, 1, 0)               features/rays.feature:22:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Scaling a ray                            features/rays.feature:24:11
[INFO] [stdout]   ✔ Given r ← ray(point(1, 2, 3), vector(0, 1, 0))   features/rays.feature:25:3
[INFO] [stdout]   ✔ And m ← scaling(2, 3, 4)                         features/rays.feature:26:5
[INFO] [stdout]   ✔ When r2 ← transform(r, m)                        features/rays.feature:27:3
[INFO] [stdout]   ✔ Then r2.origin = point(2, 6, 12)                 features/rays.feature:28:3
[INFO] [stdout]   ✔ And r2.direction = vector(0, 3, 0)               features/rays.feature:29:5
[INFO] [stdout] 
[INFO] [stdout] Feature: Abstract Shapes                            features/shapes.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The default transformation              features/shapes.feature:3:11
[INFO] [stdout]   ✔ Given s ← test_shape()                          features/shapes.feature:4:3
[INFO] [stdout]   ✔ Then s.transform = identity_matrix              features/shapes.feature:5:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Assigning a transformation              features/shapes.feature:7:11
[INFO] [stdout]   ✔ Given s ← test_shape()                          features/shapes.feature:8:3
[INFO] [stdout]   ✔ When set_transform(s, translation(2, 3, 4))     features/shapes.feature:9:3
[INFO] [stdout]   ✔ Then s.transform = translation(2, 3, 4)        features/shapes.feature:10:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The default material                   features/shapes.feature:12:11
[INFO] [stdout]   ✔ Given s ← test_shape()                         features/shapes.feature:13:3
[INFO] [stdout]   ✔ When m ← s.material                            features/shapes.feature:14:3
[INFO] [stdout]   ✔ Then m = material()                            features/shapes.feature:15:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Assigning a material                   features/shapes.feature:17:11
[INFO] [stdout]   ✔ Given s ← test_shape()                         features/shapes.feature:18:3
[INFO] [stdout]   ✔ And m ← material()                             features/shapes.feature:19:5
[INFO] [stdout]   ✔ And m.ambient ← 1                              features/shapes.feature:20:5
[INFO] [stdout]   ✔ When s.material ← m                            features/shapes.feature:21:3
[INFO] [stdout]   ✔ Then s.material = m                            features/shapes.feature:22:3
[INFO] [stdout] 
[INFO] [stdout] Feature: Spheres                                   features/spheres.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A ray intersects a sphere at two points
[INFO] [stdout]                                                   features/spheres.feature:3:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1))
[INFO] [stdout]                                                    features/spheres.feature:4:3
[INFO] [stdout]   ✔ And s ← sphere()                               features/spheres.feature:5:5
[INFO] [stdout]   ✔ When xs ← intersect(s, r)                      features/spheres.feature:6:3
[INFO] [stdout]   ✔ Then xs.count = 2                              features/spheres.feature:7:3
[INFO] [stdout]   ✔ And xs[0].t = 4.0                              features/spheres.feature:8:5
[INFO] [stdout]   ✔ And xs[1].t = 6.0                              features/spheres.feature:9:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A ray intersects a sphere at a tangent
[INFO] [stdout]                                                  features/spheres.feature:11:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 1, -5), vector(0, 0, 1))
[INFO] [stdout]                                                   features/spheres.feature:12:3
[INFO] [stdout]   ✔ And s ← sphere()                              features/spheres.feature:13:5
[INFO] [stdout]   ✔ When xs ← intersect(s, r)                     features/spheres.feature:14:3
[INFO] [stdout]   ✔ Then xs.count = 2                             features/spheres.feature:15:3
[INFO] [stdout]   ✔ And xs[0].t = 5.0                             features/spheres.feature:16:5
[INFO] [stdout]   ✔ And xs[1].t = 5.0                             features/spheres.feature:17:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A ray misses a sphere                 features/spheres.feature:19:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 2, -5), vector(0, 0, 1))
[INFO] [stdout]                                                   features/spheres.feature:20:3
[INFO] [stdout]   ✔ And s ← sphere()                              features/spheres.feature:21:5
[INFO] [stdout]   ✔ When xs ← intersect(s, r)                     features/spheres.feature:22:3
[INFO] [stdout]   ✔ Then xs.count = 0                             features/spheres.feature:23:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A ray originates inside a sphere      features/spheres.feature:25:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, 0), vector(0, 0, 1))
[INFO] [stdout]                                                   features/spheres.feature:26:3
[INFO] [stdout]   ✔ And s ← sphere()                              features/spheres.feature:27:5
[INFO] [stdout]   ✔ When xs ← intersect(s, r)                     features/spheres.feature:28:3
[INFO] [stdout]   ✔ Then xs.count = 2                             features/spheres.feature:29:3
[INFO] [stdout]   ✔ And xs[0].t = -1.0                            features/spheres.feature:30:5
[INFO] [stdout]   ✔ And xs[1].t = 1.0                             features/spheres.feature:31:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A sphere is behind a ray              features/spheres.feature:33:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, 5), vector(0, 0, 1))
[INFO] [stdout]                                                   features/spheres.feature:34:3
[INFO] [stdout]   ✔ And s ← sphere()                              features/spheres.feature:35:5
[INFO] [stdout]   ✔ When xs ← intersect(s, r)                     features/spheres.feature:36:3
[INFO] [stdout]   ✔ Then xs.count = 2                             features/spheres.feature:37:3
[INFO] [stdout]   ✔ And xs[0].t = -6.0                            features/spheres.feature:38:5
[INFO] [stdout]   ✔ And xs[1].t = -4.0                            features/spheres.feature:39:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Intersect sets the object on the intersection
[INFO] [stdout]                                                  features/spheres.feature:41:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1))
[INFO] [stdout]                                                   features/spheres.feature:42:3
[INFO] [stdout]   ✔ And s ← sphere()                              features/spheres.feature:43:5
[INFO] [stdout]   ✔ When xs ← intersect(s, r)                     features/spheres.feature:44:3
[INFO] [stdout]   ✔ Then xs.count = 2                             features/spheres.feature:45:3
[INFO] [stdout]   ✔ And xs[0].object = s                          features/spheres.feature:46:5
[INFO] [stdout]   ✔ And xs[1].object = s                          features/spheres.feature:47:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A sphere's default transformation     features/spheres.feature:49:11
[INFO] [stdout]   ✔ Given s ← sphere()                            features/spheres.feature:50:3
[INFO] [stdout]   ✔ Then s.transform = identity_matrix            features/spheres.feature:51:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Changing a sphere's transformation    features/spheres.feature:53:11
[INFO] [stdout]   ✔ Given s ← sphere()                            features/spheres.feature:54:3
[INFO] [stdout]   ✔ And t ← translation(2, 3, 4)                  features/spheres.feature:55:5
[INFO] [stdout]   ✔ When set_transform(s, t)                      features/spheres.feature:56:3
[INFO] [stdout]   ✔ Then s.transform = t                          features/spheres.feature:57:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Intersecting a scaled sphere with a ray
[INFO] [stdout]                                                  features/spheres.feature:59:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1))
[INFO] [stdout]                                                   features/spheres.feature:60:3
[INFO] [stdout]   ✔ And s ← sphere()                              features/spheres.feature:61:5
[INFO] [stdout]   ✔ When set_transform(s, scaling(2, 2, 2))       features/spheres.feature:62:3
[INFO] [stdout]   ✔ And xs ← intersect(s, r)                      features/spheres.feature:63:5
[INFO] [stdout]   ✔ Then xs.count = 2                             features/spheres.feature:64:3
[INFO] [stdout]   ✔ And xs[0].t = 3                               features/spheres.feature:65:5
[INFO] [stdout]   ✔ And xs[1].t = 7                               features/spheres.feature:66:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Intersecting a translated sphere with a ray
[INFO] [stdout]                                                  features/spheres.feature:68:11
[INFO] [stdout]   ✔ Given r ← ray(point(0, 0, -5), vector(0, 0, 1))
[INFO] [stdout]                                                   features/spheres.feature:69:3
[INFO] [stdout]   ✔ And s ← sphere()                              features/spheres.feature:70:5
[INFO] [stdout]   ✔ When set_transform(s, translation(5, 0, 0))   features/spheres.feature:71:3
[INFO] [stdout]   ✔ And xs ← intersect(s, r)                      features/spheres.feature:72:5
[INFO] [stdout]   ✔ Then xs.count = 0                             features/spheres.feature:73:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The normal on a sphere at a point on the x axis
[INFO] [stdout]                                                  features/spheres.feature:75:11
[INFO] [stdout]   ✔ Given s ← sphere()                            features/spheres.feature:76:3
[INFO] [stdout]   ✔ When n ← normal_at(s, point(1, 0, 0))         features/spheres.feature:77:3
[INFO] [stdout]   ✔ Then n = vector(1, 0, 0)                      features/spheres.feature:78:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The normal on a sphere at a point on the y axis
[INFO] [stdout]                                                  features/spheres.feature:80:11
[INFO] [stdout]   ✔ Given s ← sphere()                            features/spheres.feature:81:3
[INFO] [stdout]   ✔ When n ← normal_at(s, point(0, 1, 0))         features/spheres.feature:82:3
[INFO] [stdout]   ✔ Then n = vector(0, 1, 0)                      features/spheres.feature:83:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The normal on a sphere at a point on the z axis
[INFO] [stdout]                                                  features/spheres.feature:85:11
[INFO] [stdout]   ✔ Given s ← sphere()                            features/spheres.feature:86:3
[INFO] [stdout]   ✔ When n ← normal_at(s, point(0, 0, 1))         features/spheres.feature:87:3
[INFO] [stdout]   ✔ Then n = vector(0, 0, 1)                      features/spheres.feature:88:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The normal on a sphere at a nonaxial point
[INFO] [stdout]                                                  features/spheres.feature:90:11
[INFO] [stdout]   ✔ Given s ← sphere()                            features/spheres.feature:91:3
[INFO] [stdout]   ✔ When n ← normal_at(s, point(√3/3, √3/3, √3/3))
[INFO] [stdout]                                                   features/spheres.feature:92:3
[INFO] [stdout]   ✔ Then n = vector(√3/3, √3/3, √3/3)             features/spheres.feature:93:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The normal is a normalized vector     features/spheres.feature:95:11
[INFO] [stdout]   ✔ Given s ← sphere()                            features/spheres.feature:96:3
[INFO] [stdout]   ✔ When n ← normal_at(s, point(√3/3, √3/3, √3/3))
[INFO] [stdout]                                                   features/spheres.feature:97:3
[INFO] [stdout]   ✔ Then n = normalize(n)                         features/spheres.feature:98:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Computing the normal on a translated sphere
[INFO] [stdout]                                                 features/spheres.feature:100:11
[INFO] [stdout]   ✔ Given s ← sphere()                           features/spheres.feature:101:3
[INFO] [stdout]   ✔ And set_transform(s, translation(0, 1, 0))   features/spheres.feature:102:5
[INFO] [stdout]   ✔ When n ← normal_at(s, point(0, 1.70711, -0.70711))
[INFO] [stdout]                                                  features/spheres.feature:103:3
[INFO] [stdout]   ✔ Then n = vector(0, 0.70711, -0.70711)        features/spheres.feature:104:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Computing the normal on a transformed sphere
[INFO] [stdout]                                                 features/spheres.feature:106:11
[INFO] [stdout]   ✔ Given s ← sphere()                           features/spheres.feature:107:3
[INFO] [stdout]   ✔ And m ← scaling(1, 0.5, 1) * rotation_z(π/5) features/spheres.feature:108:5
[INFO] [stdout]   ✔ And set_transform(s, m)                      features/spheres.feature:109:5
[INFO] [stdout]   ✔ When n ← normal_at(s, point(0, √2/2, -√2/2)) features/spheres.feature:110:3
[INFO] [stdout]   ✔ Then n = vector(0, 0.97014, -0.24254)        features/spheres.feature:111:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A sphere has a default material      features/spheres.feature:113:11
[INFO] [stdout]   ✔ Given s ← sphere()                           features/spheres.feature:114:3
[INFO] [stdout]   ✔ When m ← s.material                          features/spheres.feature:115:3
[INFO] [stdout]   ✔ Then m = material()                          features/spheres.feature:116:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A sphere may be assigned a material  features/spheres.feature:118:11
[INFO] [stdout]   ✔ Given s ← sphere()                           features/spheres.feature:119:3
[INFO] [stdout]   ✔ And m ← material()                           features/spheres.feature:120:5
[INFO] [stdout]   ✔ And m.ambient ← 1                            features/spheres.feature:121:5
[INFO] [stdout]   ✔ When s.material ← m                          features/spheres.feature:122:3
[INFO] [stdout]   ✔ Then s.material = m                          features/spheres.feature:123:3
[INFO] [stdout] 
[INFO] [stdout] Feature: Matrix Transformations            features/transformations.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Multiplying by a translation matrix
[INFO] [stdout]                                           features/transformations.feature:3:11
[INFO] [stdout]   ✔ Given transform ← translation(5, -3, 2)
[INFO] [stdout]                                            features/transformations.feature:4:3
[INFO] [stdout]   ✔ And p ← point(-3, 4, 5)                features/transformations.feature:5:5
[INFO] [stdout]   ✔ Then transform * p = point(2, 1, 7)    features/transformations.feature:6:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Multiplying by the inverse of a translation matrix
[INFO] [stdout]                                           features/transformations.feature:8:11
[INFO] [stdout]   ✔ Given transform ← translation(5, -3, 2)
[INFO] [stdout]                                            features/transformations.feature:9:3
[INFO] [stdout]   ✔ And inv ← inverse(transform)          features/transformations.feature:10:5
[INFO] [stdout]   ✔ And p ← point(-3, 4, 5)               features/transformations.feature:11:5
[INFO] [stdout]   ✔ Then inv * p = point(-8, 7, 3)        features/transformations.feature:12:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Translation does not affect vectors
[INFO] [stdout]                                          features/transformations.feature:14:11
[INFO] [stdout]   ✔ Given transform ← translation(5, -3, 2)
[INFO] [stdout]                                           features/transformations.feature:15:3
[INFO] [stdout]   ✔ And v ← vector(-3, 4, 5)              features/transformations.feature:16:5
[INFO] [stdout]   ✔ Then transform * v = v                features/transformations.feature:17:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A scaling matrix applied to a point
[INFO] [stdout]                                          features/transformations.feature:19:11
[INFO] [stdout]   ✔ Given transform ← scaling(2, 3, 4)    features/transformations.feature:20:3
[INFO] [stdout]   ✔ And p ← point(-4, 6, 8)               features/transformations.feature:21:5
[INFO] [stdout]   ✔ Then transform * p = point(-8, 18, 32)
[INFO] [stdout]                                           features/transformations.feature:22:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A scaling matrix applied to a vector
[INFO] [stdout]                                          features/transformations.feature:24:11
[INFO] [stdout]   ✔ Given transform ← scaling(2, 3, 4)    features/transformations.feature:25:3
[INFO] [stdout]   ✔ And v ← vector(-4, 6, 8)              features/transformations.feature:26:5
[INFO] [stdout]   ✔ Then transform * v = vector(-8, 18, 32)
[INFO] [stdout]                                           features/transformations.feature:27:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Multiplying by the inverse of a scaling matrix
[INFO] [stdout]                                          features/transformations.feature:29:11
[INFO] [stdout]   ✔ Given transform ← scaling(2, 3, 4)    features/transformations.feature:30:3
[INFO] [stdout]   ✔ And inv ← inverse(transform)          features/transformations.feature:31:5
[INFO] [stdout]   ✔ And v ← vector(-4, 6, 8)              features/transformations.feature:32:5
[INFO] [stdout]   ✔ Then inv * v = vector(-2, 2, 2)       features/transformations.feature:33:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Reflection is scaling by a negative value
[INFO] [stdout]                                          features/transformations.feature:35:11
[INFO] [stdout]   ✔ Given transform ← scaling(-1, 1, 1)   features/transformations.feature:36:3
[INFO] [stdout]   ✔ And p ← point(2, 3, 4)                features/transformations.feature:37:5
[INFO] [stdout]   ✔ Then transform * p = point(-2, 3, 4)  features/transformations.feature:38:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Rotating a point around the x axis
[INFO] [stdout]                                          features/transformations.feature:40:11
[INFO] [stdout]   ✔ Given p ← point(0, 1, 0)              features/transformations.feature:41:3
[INFO] [stdout]   ✔ And half_quarter ← rotation_x(π / 4)  features/transformations.feature:42:5
[INFO] [stdout]   ✔ And full_quarter ← rotation_x(π / 2)  features/transformations.feature:43:5
[INFO] [stdout]   ✔ Then half_quarter * p = point(0, √2/2, √2/2)
[INFO] [stdout]                                           features/transformations.feature:44:3
[INFO] [stdout]   ✔ And full_quarter * p = point(0, 0, 1) features/transformations.feature:45:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The inverse of an x-rotation rotates in the opposite direction
[INFO] [stdout]                                          features/transformations.feature:47:11
[INFO] [stdout]   ✔ Given p ← point(0, 1, 0)              features/transformations.feature:48:3
[INFO] [stdout]   ✔ And half_quarter ← rotation_x(π / 4)  features/transformations.feature:49:5
[INFO] [stdout]   ✔ And inv ← inverse(half_quarter)       features/transformations.feature:50:5
[INFO] [stdout]   ✔ Then inv * p = point(0, √2/2, -√2/2)  features/transformations.feature:51:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Rotating a point around the y axis
[INFO] [stdout]                                          features/transformations.feature:53:11
[INFO] [stdout]   ✔ Given p ← point(0, 0, 1)              features/transformations.feature:54:3
[INFO] [stdout]   ✔ And half_quarter ← rotation_y(π / 4)  features/transformations.feature:55:5
[INFO] [stdout]   ✔ And full_quarter ← rotation_y(π / 2)  features/transformations.feature:56:5
[INFO] [stdout]   ✔ Then half_quarter * p = point(√2/2, 0, √2/2)
[INFO] [stdout]                                           features/transformations.feature:57:3
[INFO] [stdout]   ✔ And full_quarter * p = point(1, 0, 0) features/transformations.feature:58:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Rotating a point around the z axis
[INFO] [stdout]                                          features/transformations.feature:60:11
[INFO] [stdout]   ✔ Given p ← point(0, 1, 0)              features/transformations.feature:61:3
[INFO] [stdout]   ✔ And half_quarter ← rotation_z(π / 4)  features/transformations.feature:62:5
[INFO] [stdout]   ✔ And full_quarter ← rotation_z(π / 2)  features/transformations.feature:63:5
[INFO] [stdout]   ✔ Then half_quarter * p = point(-√2/2, √2/2, 0)
[INFO] [stdout]                                           features/transformations.feature:64:3
[INFO] [stdout]   ✔ And full_quarter * p = point(-1, 0, 0)
[INFO] [stdout]                                           features/transformations.feature:65:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A shearing transformation moves x in proportion to y
[INFO] [stdout]                                          features/transformations.feature:67:11
[INFO] [stdout]   ✔ Given transform ← shearing(1, 0, 0, 0, 0, 0)
[INFO] [stdout]                                           features/transformations.feature:68:3
[INFO] [stdout]   ✔ And p ← point(2, 3, 4)                features/transformations.feature:69:5
[INFO] [stdout]   ✔ Then transform * p = point(5, 3, 4)   features/transformations.feature:70:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A shearing transformation moves x in proportion to z
[INFO] [stdout]                                          features/transformations.feature:72:11
[INFO] [stdout]   ✔ Given transform ← shearing(0, 1, 0, 0, 0, 0)
[INFO] [stdout]                                           features/transformations.feature:73:3
[INFO] [stdout]   ✔ And p ← point(2, 3, 4)                features/transformations.feature:74:5
[INFO] [stdout]   ✔ Then transform * p = point(6, 3, 4)   features/transformations.feature:75:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A shearing transformation moves y in proportion to x
[INFO] [stdout]                                          features/transformations.feature:77:11
[INFO] [stdout]   ✔ Given transform ← shearing(0, 0, 1, 0, 0, 0)
[INFO] [stdout]                                           features/transformations.feature:78:3
[INFO] [stdout]   ✔ And p ← point(2, 3, 4)                features/transformations.feature:79:5
[INFO] [stdout]   ✔ Then transform * p = point(2, 5, 4)   features/transformations.feature:80:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A shearing transformation moves y in proportion to z
[INFO] [stdout]                                          features/transformations.feature:82:11
[INFO] [stdout]   ✔ Given transform ← shearing(0, 0, 0, 1, 0, 0)
[INFO] [stdout]                                           features/transformations.feature:83:3
[INFO] [stdout]   ✔ And p ← point(2, 3, 4)                features/transformations.feature:84:5
[INFO] [stdout]   ✔ Then transform * p = point(2, 7, 4)   features/transformations.feature:85:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A shearing transformation moves z in proportion to x
[INFO] [stdout]                                          features/transformations.feature:87:11
[INFO] [stdout]   ✔ Given transform ← shearing(0, 0, 0, 0, 1, 0)
[INFO] [stdout]                                           features/transformations.feature:88:3
[INFO] [stdout]   ✔ And p ← point(2, 3, 4)                features/transformations.feature:89:5
[INFO] [stdout]   ✔ Then transform * p = point(2, 3, 6)   features/transformations.feature:90:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A shearing transformation moves z in proportion to y
[INFO] [stdout]                                          features/transformations.feature:92:11
[INFO] [stdout]   ✔ Given transform ← shearing(0, 0, 0, 0, 0, 1)
[INFO] [stdout]                                           features/transformations.feature:93:3
[INFO] [stdout]   ✔ And p ← point(2, 3, 4)                features/transformations.feature:94:5
[INFO] [stdout]   ✔ Then transform * p = point(2, 3, 7)   features/transformations.feature:95:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Individual transformations are applied in sequence
[INFO] [stdout]                                          features/transformations.feature:97:11
[INFO] [stdout]   ✔ Given p ← point(1, 0, 1)              features/transformations.feature:98:3
[INFO] [stdout]   ✔ And A ← rotation_x(π / 2)             features/transformations.feature:99:5
[INFO] [stdout]   ✔ And B ← scaling(5, 5, 5)             features/transformations.feature:100:5
[INFO] [stdout]   ✔ And C ← translation(10, 5, 7)        features/transformations.feature:101:5
[INFO] [stdout]   ✔ When p2 ← A * p                      features/transformations.feature:103:3
[INFO] [stdout]   ✔ Then p2 = point(1, -1, 0)            features/transformations.feature:104:3
[INFO] [stdout]   ✔ When p3 ← B * p2                     features/transformations.feature:106:3
[INFO] [stdout]   ✔ Then p3 = point(5, -5, 0)            features/transformations.feature:107:3
[INFO] [stdout]   ✔ When p4 ← C * p3                     features/transformations.feature:109:3
[INFO] [stdout]   ✔ Then p4 = point(15, 0, 7)            features/transformations.feature:110:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Chained transformations must be applied in reverse order
[INFO] [stdout]                                         features/transformations.feature:112:11
[INFO] [stdout]   ✔ Given p ← point(1, 0, 1)             features/transformations.feature:113:3
[INFO] [stdout]   ✔ And A ← rotation_x(π / 2)            features/transformations.feature:114:5
[INFO] [stdout]   ✔ And B ← scaling(5, 5, 5)             features/transformations.feature:115:5
[INFO] [stdout]   ✔ And C ← translation(10, 5, 7)        features/transformations.feature:116:5
[INFO] [stdout]   ✔ When T ← C * B * A                   features/transformations.feature:117:3
[INFO] [stdout]   ✔ Then T * p = point(15, 0, 7)         features/transformations.feature:118:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The transformation matrix for the default orientation
[INFO] [stdout]                                         features/transformations.feature:120:11
[INFO] [stdout]   ✔ Given from ← point(0, 0, 0)          features/transformations.feature:121:3
[INFO] [stdout]   ✔ And to ← point(0, 0, -1)             features/transformations.feature:122:5
[INFO] [stdout]   ✔ And up ← vector(0, 1, 0)             features/transformations.feature:123:5
[INFO] [stdout]   ✔ When t ← view_transform(from, to, up)
[INFO] [stdout]                                          features/transformations.feature:124:3
[INFO] [stdout]   ✔ Then t = identity_matrix             features/transformations.feature:125:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: A view transformation matrix looking in positive z direction
[INFO] [stdout]                                         features/transformations.feature:127:11
[INFO] [stdout]   ✔ Given from ← point(0, 0, 0)          features/transformations.feature:128:3
[INFO] [stdout]   ✔ And to ← point(0, 0, 1)              features/transformations.feature:129:5
[INFO] [stdout]   ✔ And up ← vector(0, 1, 0)             features/transformations.feature:130:5
[INFO] [stdout]   ✔ When t ← view_transform(from, to, up)
[INFO] [stdout]                                          features/transformations.feature:131:3
[INFO] [stdout]   ✔ Then t = scaling(-1, 1, -1)          features/transformations.feature:132:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The view transformation moves the world
[INFO] [stdout]                                         features/transformations.feature:134:11
[INFO] [stdout]   ✔ Given from ← point(0, 0, 8)          features/transformations.feature:135:3
[INFO] [stdout]   ✔ And to ← point(0, 0, 0)              features/transformations.feature:136:5
[INFO] [stdout]   ✔ And up ← vector(0, 1, 0)             features/transformations.feature:137:5
[INFO] [stdout]   ✔ When t ← view_transform(from, to, up)
[INFO] [stdout]                                          features/transformations.feature:138:3
[INFO] [stdout]   ✔ Then t = translation(0, 0, -8)       features/transformations.feature:139:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: An arbitrary view transformation
[INFO] [stdout]                                         features/transformations.feature:141:11
[INFO] [stdout]   ✔ Given from ← point(1, 3, 2)          features/transformations.feature:142:3
[INFO] [stdout]   ✔ And to ← point(4, -2, 8)             features/transformations.feature:143:5
[INFO] [stdout]   ✔ And up ← vector(1, 1, 0)             features/transformations.feature:144:5
[INFO] [stdout]   ✔ When t ← view_transform(from, to, up)
[INFO] [stdout]                                          features/transformations.feature:145:3
[INFO] [stdout]   ✔ Then t is the following 4x4 matrix:  features/transformations.feature:146:3
[INFO] [stdout]       | -0.50709 | 0.50709 | 0.67612  | -2.36643 |
[INFO] [stdout]       |  0.76772 | 0.60609 |  0.12122 | -2.82843 |
[INFO] [stdout]       | -0.35857 | 0.59761 | -0.71714 |  0.00000 |
[INFO] [stdout]       |  0.00000 | 0.00000 |  0.00000 |  1.00000 |
[INFO] [stdout] 
[INFO] [stdout] Feature: Tuples, Vectors, and Points                features/tuples.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Reflecting a vector approaching at 45°
[INFO] [stdout]                                                  features/tuples.feature:140:11
[INFO] [stdout]   ✔ Given v ← vector(1, -1, 0)                    features/tuples.feature:141:3
[INFO] [stdout]   ✔ And n ← vector(0, 1, 0)                       features/tuples.feature:142:5
[INFO] [stdout]   ✔ When r ← reflect(v, n)                        features/tuples.feature:143:3
[INFO] [stdout]   ✔ Then r = vector(1, 1, 0)                      features/tuples.feature:144:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Reflecting a vector off a slanted surface
[INFO] [stdout]                                                  features/tuples.feature:146:11
[INFO] [stdout]   ✔ Given v ← vector(0, -1, 0)                    features/tuples.feature:147:3
[INFO] [stdout]   ✔ And n ← vector(√2/2, √2/2, 0)                 features/tuples.feature:148:5
[INFO] [stdout]   ✔ When r ← reflect(v, n)                        features/tuples.feature:149:3
[INFO] [stdout]   ✔ Then r = vector(1, 0, 0)                      features/tuples.feature:150:3
[INFO] [stdout] 
[INFO] [stdout] Feature: World                                       features/world.feature:1:1
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Creating a world                         features/world.feature:3:11
[INFO] [stdout]   ✔ Given w ← world()                                features/world.feature:4:3
[INFO] [stdout]   ✔ Then w contains no objects                       features/world.feature:5:3
[INFO] [stdout]   ✔ And w has no light source                        features/world.feature:6:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The default world                        features/world.feature:8:11
[INFO] [stdout]   ✔ Given light ← point_light(point(-10, 10, -10), color(1, 1, 1))
[INFO] [stdout]                                                      features/world.feature:9:3
[INFO] [stdout]   ✔ And s1 ← sphere() with:                         features/world.feature:10:5
[INFO] [stdout]       | material.color    | (0.8, 1.0, 0.6) |
[INFO] [stdout]       | material.diffuse  |             0.7 |
[INFO] [stdout]       | material.specular |             0.2 |
[INFO] [stdout]   ✔ And s2 ← sphere() with:                         features/world.feature:14:5
[INFO] [stdout]       |           |                        |
[INFO] [stdout]       | transform | scaling(0.5, 0.5, 0.5) |
[INFO] [stdout]   ✔ When w ← default_world()                        features/world.feature:16:3
[INFO] [stdout]   ✔ Then w.light = light                            features/world.feature:17:3
[INFO] [stdout]   ✔ And w contains s1                               features/world.feature:18:5
[INFO] [stdout]   ✔ And w contains s2                               features/world.feature:19:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Intersect a world with a ray            features/world.feature:21:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:22:3
[INFO] [stdout]   ✔ And r ← ray(point(0, 0, -5), vector(0, 0, 1))   features/world.feature:23:5
[INFO] [stdout]   ✔ When xs ← intersect_world(w, r)                 features/world.feature:24:3
[INFO] [stdout]   ✔ Then xs.count = 4                               features/world.feature:25:3
[INFO] [stdout]   ✔ And xs[0].t = 4                                 features/world.feature:26:5
[INFO] [stdout]   ✔ And xs[1].t = 4.5                               features/world.feature:27:5
[INFO] [stdout]   ✔ And xs[2].t = 5.5                               features/world.feature:28:5
[INFO] [stdout]   ✔ And xs[3].t = 6                                 features/world.feature:29:5
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Shading an intersection                 features/world.feature:31:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:32:3
[INFO] [stdout]   ✔ And r ← ray(point(0, 0, -5), vector(0, 0, 1))   features/world.feature:33:5
[INFO] [stdout]   ✔ And shape ← the first object in w               features/world.feature:34:5
[INFO] [stdout]   ✔ And i ← intersection(4, shape)                  features/world.feature:35:5
[INFO] [stdout]   ✔ When comps ← prepare_computations(i, r)         features/world.feature:36:3
[INFO] [stdout]   ✔ And c ← shade_hit(w, comps)                     features/world.feature:37:5
[INFO] [stdout]   ✔ Then c = color(0.38066, 0.47583, 0.2855)        features/world.feature:38:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: Shading an intersection from the inside features/world.feature:40:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:41:3
[INFO] [stdout]   ✔ And w.light ← point_light(point(0, 0.25, 0), color(1, 1, 1))
[INFO] [stdout]                                                     features/world.feature:42:5
[INFO] [stdout]   ✔ And r ← ray(point(0, 0, 0), vector(0, 0, 1))    features/world.feature:43:5
[INFO] [stdout]   ✔ And shape ← the second object in w              features/world.feature:44:5
[INFO] [stdout]   ✔ And i ← intersection(0.5, shape)                features/world.feature:45:5
[INFO] [stdout]   ✔ When comps ← prepare_computations(i, r)         features/world.feature:46:3
[INFO] [stdout]   ✔ And c ← shade_hit(w, comps)                     features/world.feature:47:5
[INFO] [stdout]   ✔ Then c = color(0.90498, 0.90498, 0.90498)       features/world.feature:48:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The color when a ray misses             features/world.feature:50:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:51:3
[INFO] [stdout]   ✔ And r ← ray(point(0, 0, -5), vector(0, 1, 0))   features/world.feature:52:5
[INFO] [stdout]   ✔ When c ← color_at(w, r)                         features/world.feature:53:3
[INFO] [stdout]   ✔ Then c = color(0, 0, 0)                         features/world.feature:54:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The color when a ray hits               features/world.feature:56:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:57:3
[INFO] [stdout]   ✔ And r ← ray(point(0, 0, -5), vector(0, 0, 1))   features/world.feature:58:5
[INFO] [stdout]   ✔ When c ← color_at(w, r)                         features/world.feature:59:3
[INFO] [stdout]   ✔ Then c = color(0.38066, 0.47583, 0.2855)        features/world.feature:60:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The color with an intersection behind the ray
[INFO] [stdout]                                                    features/world.feature:62:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:63:3
[INFO] [stdout]   ✔ And outer ← the first object in w               features/world.feature:64:5
[INFO] [stdout]   ✔ And outer.material.ambient ← 1                  features/world.feature:65:5
[INFO] [stdout]   ✔ And inner ← the second object in w              features/world.feature:66:5
[INFO] [stdout]   ✔ And inner.material.ambient ← 1                  features/world.feature:67:5
[INFO] [stdout]   ✔ And r ← ray(point(0, 0, 0.75), vector(0, 0, -1))
[INFO] [stdout]                                                     features/world.feature:68:5
[INFO] [stdout]   ✔ When c ← color_at(w, r)                         features/world.feature:69:3
[INFO] [stdout]   ✔ Then c = inner.material.color                   features/world.feature:70:3
[INFO] [stdout] 
[INFO] [stdout]  Scenario: There is no shadow when nothing is collinear with point and light
[INFO] [stdout]                                                    features/world.feature:72:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:73:3
[INFO] [stdout]   ✔ And p ← point(0, 10, 0)                         features/world.feature:74:5
[INFO] [stdout]   ✔ Then is_shadowed(w, p) is false                 features/world.feature:75:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: The shadow when an object is between the point and the light
[INFO] [stdout]                                                    features/world.feature:77:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:78:3
[INFO] [stdout]   ✔ And p ← point(10, -10, 10)                      features/world.feature:79:5
[INFO] [stdout]   ✔ Then is_shadowed(w, p) is true                  features/world.feature:80:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: There is no shadow when an object is behind the light
[INFO] [stdout]                                                    features/world.feature:82:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:83:3
[INFO] [stdout]   ✔ And p ← point(-20, 20, -20)                     features/world.feature:84:5
[INFO] [stdout]   ✔ Then is_shadowed(w, p) is false                 features/world.feature:85:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: There is no shadow when an object is behind the point
[INFO] [stdout]                                                    features/world.feature:87:11
[INFO] [stdout]   ✔ Given w ← default_world()                       features/world.feature:88:3
[INFO] [stdout]   ✔ And p ← point(-2, 2, -2)                        features/world.feature:89:5
[INFO] [stdout]   ✔ Then is_shadowed(w, p) is false                 features/world.feature:90:4
[INFO] [stdout] 
[INFO] [stdout]  Scenario: shade_hit() is given an intersection in shadow
[INFO] [stdout]                                                    features/world.feature:92:11
[INFO] [stdout]   ✔ Given w ← world()                               features/world.feature:93:3
[INFO] [stdout]   ✔ And w.light ← point_light(point(0, 0, -10), color(1, 1, 1))
[INFO] [stdout]                                                     features/world.feature:94:5
[INFO] [stdout]   ✔ And s1 ← sphere()                               features/world.feature:95:5
[INFO] [stdout]   ✔ And s1 is added to w                            features/world.feature:96:5
[INFO] [stdout]   ✔ And s2 ← sphere() with:                         features/world.feature:97:5
[INFO] [stdout]       |           |                       |
[INFO] [stdout]       | transform | translation(0, 0, 10) |
[INFO] [stdout]   ✔ And s2 is added to w                            features/world.feature:99:5
[INFO] [stdout]   ✔ And r ← ray(point(0, 0, 5), vector(0, 0, 1))   features/world.feature:100:5
[INFO] [stdout]   ✔ And i ← intersection(4, s2)                    features/world.feature:101:5
[INFO] [stdout]   ✔ When comps ← prepare_computations(i, r)        features/world.feature:102:3
[INFO] [stdout]   ✔ And c ← shade_hit(w, comps)                    features/world.feature:103:5
[INFO] [stdout]   ✔ Then c = color(0.1, 0.1, 0.1)                  features/world.feature:104:3
[INFO] [stdout] 
[INFO] [stdout] 13 features
[INFO] [stdout] 118 scenarios (118 passed)
[INFO] [stdout] 558 steps (558 passed)
[INFO] [stdout] 
[INFO] [stderr]    Doc-tests ray_tracer_challenge
[INFO] [stderr] warning: unnecessary braces around method argument
[INFO] [stderr]   --> src/lib.rs:86:30
[INFO] [stderr]    |
[INFO] [stderr] 86 |         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stderr]    |                              ^^                      ^^
[INFO] [stderr]    |
[INFO] [stderr]    = note: `#[warn(unused_braces)]` (part of `#[warn(unused)]`) on by default
[INFO] [stderr] help: remove these braces
[INFO] [stderr]    |
[INFO] [stderr] 86 -         self.objs.iter().map({ |o| Rc::new(o.clone()) }).collect()
[INFO] [stderr] 86 +         self.objs.iter().map(|o| Rc::new(o.clone())).collect()
[INFO] [stderr]    |
[INFO] [stderr] 
[INFO] [stderr] warning: 1 warning emitted
[INFO] [stderr] 
[INFO] [stdout] 
[INFO] [stdout] running 0 tests
[INFO] [stdout] 
[INFO] [stdout] test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
[INFO] [stdout] 
[INFO] running `Command { std: "docker" "inspect" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "rm" "-f" "54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7", kill_on_drop: false }`
[INFO] [stdout] 54945999a57867a58ebe1075bf8363f4c6b4a8aa9c092284949f739c9b8c82c7
