cucover 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Licence.txt +22 -0
- data/README.markdown +69 -0
- data/Rakefile +86 -0
- data/VERSION +1 -0
- data/bin/cucover +3 -0
- data/cucover.gemspec +182 -0
- data/cucumber.yml +3 -0
- data/examples/self_test/rails/.gitignore +4 -0
- data/examples/self_test/rails/Rakefile +10 -0
- data/examples/self_test/rails/app/controllers/application_controller.rb +2 -0
- data/examples/self_test/rails/app/controllers/widgets_controller.rb +2 -0
- data/examples/self_test/rails/app/helpers/application_helper.rb +3 -0
- data/examples/self_test/rails/app/views/widgets/index.html.erb +1 -0
- data/examples/self_test/rails/config/boot.rb +110 -0
- data/examples/self_test/rails/config/cucumber.yml +7 -0
- data/examples/self_test/rails/config/database.yml +25 -0
- data/examples/self_test/rails/config/environment.rb +41 -0
- data/examples/self_test/rails/config/environments/cucumber.rb +27 -0
- data/examples/self_test/rails/config/environments/development.rb +17 -0
- data/examples/self_test/rails/config/environments/production.rb +28 -0
- data/examples/self_test/rails/config/environments/test.rb +28 -0
- data/examples/self_test/rails/config/initializers/backtrace_silencers.rb +7 -0
- data/examples/self_test/rails/config/initializers/inflections.rb +10 -0
- data/examples/self_test/rails/config/initializers/mime_types.rb +5 -0
- data/examples/self_test/rails/config/initializers/new_rails_defaults.rb +21 -0
- data/examples/self_test/rails/config/initializers/session_store.rb +15 -0
- data/examples/self_test/rails/config/locales/en.yml +5 -0
- data/examples/self_test/rails/config/routes.rb +43 -0
- data/examples/self_test/rails/db/seeds.rb +7 -0
- data/examples/self_test/rails/features/see_widgets.feature +7 -0
- data/examples/self_test/rails/features/step_definitions/web_steps.rb +273 -0
- data/examples/self_test/rails/features/support/env.rb +57 -0
- data/examples/self_test/rails/features/support/paths.rb +29 -0
- data/examples/self_test/rails/lib/tasks/cucumber.rake +47 -0
- data/examples/self_test/rails/public/404.html +30 -0
- data/examples/self_test/rails/public/422.html +30 -0
- data/examples/self_test/rails/public/500.html +30 -0
- data/examples/self_test/rails/public/favicon.ico +0 -0
- data/examples/self_test/rails/public/images/rails.png +0 -0
- data/examples/self_test/rails/public/index.html +275 -0
- data/examples/self_test/rails/public/javascripts/application.js +2 -0
- data/examples/self_test/rails/public/javascripts/controls.js +963 -0
- data/examples/self_test/rails/public/javascripts/dragdrop.js +973 -0
- data/examples/self_test/rails/public/javascripts/effects.js +1128 -0
- data/examples/self_test/rails/public/javascripts/prototype.js +4320 -0
- data/examples/self_test/rails/public/robots.txt +5 -0
- data/examples/self_test/rails/script/about +4 -0
- data/examples/self_test/rails/script/console +3 -0
- data/examples/self_test/rails/script/cucumber +10 -0
- data/examples/self_test/rails/script/dbconsole +3 -0
- data/examples/self_test/rails/script/destroy +3 -0
- data/examples/self_test/rails/script/generate +3 -0
- data/examples/self_test/rails/script/performance/benchmarker +3 -0
- data/examples/self_test/rails/script/performance/profiler +3 -0
- data/examples/self_test/rails/script/plugin +3 -0
- data/examples/self_test/rails/script/runner +3 -0
- data/examples/self_test/rails/script/server +3 -0
- data/examples/self_test/simple/features/call_foo.feature +4 -0
- data/examples/self_test/simple/features/call_foo_and_bar_together.feature +5 -0
- data/examples/self_test/simple/features/call_foo_from_background_then_bar.feature +7 -0
- data/examples/self_test/simple/features/call_foo_from_background_then_bar_then_baz.feature +10 -0
- data/examples/self_test/simple/features/call_foo_then_bar.feature +7 -0
- data/examples/self_test/simple/features/call_foo_then_bar_from_scenario_outline_examples.feature +9 -0
- data/examples/self_test/simple/features/fail.feature +10 -0
- data/examples/self_test/simple/features/step_definitions/main_steps.rb +15 -0
- data/examples/self_test/simple/lib/bar.rb +5 -0
- data/examples/self_test/simple/lib/baz.rb +5 -0
- data/examples/self_test/simple/lib/foo.rb +9 -0
- data/features/call_foo.feature +0 -0
- data/features/coverage_of.feature +81 -0
- data/features/fail.feature +60 -0
- data/features/help.feature +15 -0
- data/features/lazy_run.feature +76 -0
- data/features/lazy_run_per_scenario.feature +108 -0
- data/features/lazy_run_per_scenario_outline_example.feature +29 -0
- data/features/lazy_run_triggered_by_rails_view_change.feature +43 -0
- data/features/run.feature +25 -0
- data/features/show_recordings.feature +28 -0
- data/features/step_definitions/main_steps.rb +41 -0
- data/features/support/env.rb +52 -0
- data/lib/at_exit_hook.rb +3 -0
- data/lib/cucover.rb +65 -0
- data/lib/cucover/cli.rb +50 -0
- data/lib/cucover/cli_commands/coverage_of.rb +44 -0
- data/lib/cucover/cli_commands/cucumber.rb +46 -0
- data/lib/cucover/cli_commands/show_recordings.rb +29 -0
- data/lib/cucover/cli_commands/version.rb +13 -0
- data/lib/cucover/controller.rb +28 -0
- data/lib/cucover/cucumber_hooks.rb +45 -0
- data/lib/cucover/line_numbers.rb +10 -0
- data/lib/cucover/logging_config.rb +16 -0
- data/lib/cucover/monkey.rb +14 -0
- data/lib/cucover/rails.rb +23 -0
- data/lib/cucover/recorder.rb +37 -0
- data/lib/cucover/recording.rb +66 -0
- data/lib/cucover/recording/covered_file.rb +53 -0
- data/lib/cucover/store.rb +70 -0
- data/lib/dependencies.rb +10 -0
- data/spec/cucover/cli_spec.rb +31 -0
- data/spec/cucover/controller_spec.rb +16 -0
- data/spec/cucover/recording/covered_file_spec.rb +20 -0
- data/spec/cucover/store_spec.rb +28 -0
- data/spec/spec_helper.rb +7 -0
- data/tmp/.gitignore +0 -0
- metadata +236 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
|
4
|
+
if vendored_cucumber_bin
|
5
|
+
load File.expand_path(vendored_cucumber_bin)
|
6
|
+
else
|
7
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
8
|
+
require 'cucumber'
|
9
|
+
load Cucumber::BINARY
|
10
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../lib/foo'
|
2
|
+
require File.dirname(__FILE__) + '/../../lib/bar'
|
3
|
+
require File.dirname(__FILE__) + '/../../lib/baz'
|
4
|
+
|
5
|
+
Given /^I have called ([\w]+)$/ do |class_name|
|
6
|
+
Object.const_get(class_name).new.execute
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^I call ([\w]+)$/ do |class_name|
|
10
|
+
Given "I have called #{class_name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
When /^I divide by zero$/ do
|
14
|
+
1/0
|
15
|
+
end
|
File without changes
|
@@ -0,0 +1,81 @@
|
|
1
|
+
Feature: Coverage Of
|
2
|
+
In order to find out how well tested a source file that I'm working on is
|
3
|
+
As a developer
|
4
|
+
I want to be able to ask Cucover which features cover which lines of a given source file
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given I am using the simple example app
|
8
|
+
|
9
|
+
@ruby_1_8
|
10
|
+
Scenario: Get coverage of a source file that's partly touched by the feature that was run
|
11
|
+
Given I have run cucover -- features/call_foo.feature
|
12
|
+
When I run cucover --coverage-of lib/foo.rb
|
13
|
+
Then it should pass with:
|
14
|
+
"""
|
15
|
+
1 class Foo
|
16
|
+
2 def execute features/call_foo.feature:3
|
17
|
+
3 true features/call_foo.feature:3
|
18
|
+
4 end
|
19
|
+
5
|
20
|
+
6 def sloppy_me..
|
21
|
+
7 1/0
|
22
|
+
8 end
|
23
|
+
9 end
|
24
|
+
|
25
|
+
"""
|
26
|
+
|
27
|
+
@ruby_1_8
|
28
|
+
Scenario: Run another feature that also covers the same source file
|
29
|
+
Given I have run cucover -- features/call_foo.feature
|
30
|
+
And I have run cucover -- features/call_foo_and_bar_together.feature
|
31
|
+
When I run cucover --coverage-of lib/foo.rb
|
32
|
+
Then it should pass with:
|
33
|
+
"""
|
34
|
+
1 class Foo
|
35
|
+
2 def execute features/call_foo_and_bar_together.feature:3, features/call_foo.feature:3
|
36
|
+
3 true features/call_foo_and_bar_together.feature:3, features/call_foo.feature:3
|
37
|
+
4 end
|
38
|
+
5
|
39
|
+
6 def sloppy_me..
|
40
|
+
7 1/0
|
41
|
+
8 end
|
42
|
+
9 end
|
43
|
+
|
44
|
+
"""
|
45
|
+
|
46
|
+
@ruby_1_9
|
47
|
+
Scenario: Get coverage of a source file that's partly touched by the feature that was run
|
48
|
+
Given I have run cucover -- features/call_foo.feature
|
49
|
+
When I run cucover --coverage-of lib/foo.rb
|
50
|
+
Then it should pass with:
|
51
|
+
"""
|
52
|
+
1 class Foo
|
53
|
+
2 def execute features/call_foo.feature:3
|
54
|
+
3 true features/call_foo.feature:3
|
55
|
+
4 end features/call_foo.feature:3
|
56
|
+
5
|
57
|
+
6 def sloppy_me..
|
58
|
+
7 1/0
|
59
|
+
8 end
|
60
|
+
9 end
|
61
|
+
|
62
|
+
"""
|
63
|
+
|
64
|
+
@ruby_1_9
|
65
|
+
Scenario: Run another feature that also covers the same source file
|
66
|
+
Given I have run cucover -- features/call_foo.feature
|
67
|
+
And I have run cucover -- features/call_foo_and_bar_together.feature
|
68
|
+
When I run cucover --coverage-of lib/foo.rb
|
69
|
+
Then it should pass with:
|
70
|
+
"""
|
71
|
+
1 class Foo
|
72
|
+
2 def execute features/call_foo.feature:3, features/call_foo_and_bar_together.feature:3
|
73
|
+
3 true features/call_foo.feature:3, features/call_foo_and_bar_together.feature:3
|
74
|
+
4 end features/call_foo.feature:3, features/call_foo_and_bar_together.feature:3
|
75
|
+
5
|
76
|
+
6 def sloppy_me..
|
77
|
+
7 1/0
|
78
|
+
8 end
|
79
|
+
9 end
|
80
|
+
|
81
|
+
"""
|
@@ -0,0 +1,60 @@
|
|
1
|
+
Feature: Fail
|
2
|
+
In order to diagnose a fault
|
3
|
+
As a developer
|
4
|
+
I want to get useful information when a feature fails
|
5
|
+
|
6
|
+
Scenario: Two features, one fails
|
7
|
+
And I am using the simple example app
|
8
|
+
When I run cucover -- features/call_foo.feature features/fail.feature
|
9
|
+
Then it should fail with:
|
10
|
+
"""
|
11
|
+
Feature: Call Foo
|
12
|
+
|
13
|
+
Scenario: Call Foo # features/call_foo.feature:3
|
14
|
+
When I call Foo # features/step_definitions/main_steps.rb:9
|
15
|
+
|
16
|
+
Feature: Epic Fail
|
17
|
+
In order to make my job look really hard
|
18
|
+
As a developer
|
19
|
+
I want tests to fail from time to time
|
20
|
+
|
21
|
+
Scenario: Do something stupid # features/fail.feature:6
|
22
|
+
When I divide by zero # features/step_definitions/main_steps.rb:13
|
23
|
+
divided by 0 (ZeroDivisionError)
|
24
|
+
./features/step_definitions/main_steps.rb:14:in `/'
|
25
|
+
./features/step_definitions/main_steps.rb:14:in `/^I divide by zero$/'
|
26
|
+
features/fail.feature:7:in `When I divide by zero'
|
27
|
+
|
28
|
+
Failing Scenarios:
|
29
|
+
cucumber features/fail.feature:6 # Scenario: Do something stupid
|
30
|
+
|
31
|
+
2 scenarios (1 failed, 1 passed)
|
32
|
+
2 steps (1 failed, 1 passed)
|
33
|
+
|
34
|
+
"""
|
35
|
+
|
36
|
+
Scenario: Run failing feature twice
|
37
|
+
And I am using the simple example app
|
38
|
+
And I have tried to run cucover -- features/fail.feature
|
39
|
+
When I run cucover -- features/fail.feature
|
40
|
+
Then it should fail with:
|
41
|
+
"""
|
42
|
+
Feature: Epic Fail
|
43
|
+
In order to make my job look really hard
|
44
|
+
As a developer
|
45
|
+
I want tests to fail from time to time
|
46
|
+
|
47
|
+
Scenario: Do something stupid # features/fail.feature:6
|
48
|
+
When I divide by zero # features/step_definitions/main_steps.rb:13
|
49
|
+
divided by 0 (ZeroDivisionError)
|
50
|
+
./features/step_definitions/main_steps.rb:14:in `/'
|
51
|
+
./features/step_definitions/main_steps.rb:14:in `/^I divide by zero$/'
|
52
|
+
features/fail.feature:7:in `When I divide by zero'
|
53
|
+
|
54
|
+
Failing Scenarios:
|
55
|
+
cucumber features/fail.feature:6 # Scenario: Do something stupid
|
56
|
+
|
57
|
+
1 scenario (1 failed)
|
58
|
+
1 step (1 failed)
|
59
|
+
|
60
|
+
"""
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Feature: Showing help
|
2
|
+
In order to harness the full power of cucover
|
3
|
+
As a developer
|
4
|
+
I want to see help explaining cucovers usage
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given I am using the simple example app
|
8
|
+
|
9
|
+
Scenario: --help
|
10
|
+
When I run cucover --help
|
11
|
+
Then I should see "Usage: cucover -- [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]+"
|
12
|
+
|
13
|
+
Scenario: invalid command line handle
|
14
|
+
When I run cucover --invalid-thingy
|
15
|
+
Then I should see "Usage: cucover -- [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]+"
|
@@ -0,0 +1,76 @@
|
|
1
|
+
Feature: Lazy Run
|
2
|
+
In order to get rapid feedback, stay motivated and keep my concentration
|
3
|
+
As a developer
|
4
|
+
I want to be able to run only the features that could have been affected by the changes I've made
|
5
|
+
|
6
|
+
Background:
|
7
|
+
And I am using the simple example app
|
8
|
+
And I have run cucover -- features/call_foo.feature
|
9
|
+
|
10
|
+
Scenario: Change nothing and run same feature again
|
11
|
+
When I run cucover -- -q features/call_foo.feature
|
12
|
+
Then it should pass with:
|
13
|
+
"""
|
14
|
+
Feature: Call Foo
|
15
|
+
|
16
|
+
Scenario: Call Foo
|
17
|
+
When I call Foo
|
18
|
+
[ Cucover - Skipping clean scenario ]
|
19
|
+
|
20
|
+
1 scenario (1 skipped)
|
21
|
+
1 step (1 skipped)
|
22
|
+
|
23
|
+
"""
|
24
|
+
|
25
|
+
Scenario: Change irrelevant source file and run same feature again
|
26
|
+
When I edit the source file lib/bar.rb
|
27
|
+
When I run cucover -- features/call_foo.feature
|
28
|
+
Then it should pass with:
|
29
|
+
"""
|
30
|
+
Feature: Call Foo
|
31
|
+
|
32
|
+
Scenario: Call Foo # features/call_foo.feature:3
|
33
|
+
When I call Foo # features/step_definitions/main_steps.rb:9
|
34
|
+
[ Cucover - Skipping clean scenario ]
|
35
|
+
|
36
|
+
1 scenario (1 skipped)
|
37
|
+
1 step (1 skipped)
|
38
|
+
|
39
|
+
"""
|
40
|
+
|
41
|
+
Scenario: Touch feature file and run same feature again
|
42
|
+
When I edit the source file features/call_foo.feature
|
43
|
+
And I run cucover -- features/call_foo.feature
|
44
|
+
Then it should pass with:
|
45
|
+
"""
|
46
|
+
Feature: Call Foo
|
47
|
+
|
48
|
+
Scenario: Call Foo # features/call_foo.feature:3
|
49
|
+
When I call Foo # features/step_definitions/main_steps.rb:9
|
50
|
+
|
51
|
+
1 scenario (1 passed)
|
52
|
+
1 step (1 passed)
|
53
|
+
|
54
|
+
"""
|
55
|
+
|
56
|
+
Scenario: Touch one source file and try to run lots of features
|
57
|
+
When I edit the source file lib/bar.rb
|
58
|
+
And I run cucover -- features/call_foo.feature features/call_foo_and_bar_together.feature
|
59
|
+
Then it should pass with:
|
60
|
+
"""
|
61
|
+
Feature: Call Foo
|
62
|
+
|
63
|
+
Scenario: Call Foo # features/call_foo.feature:3
|
64
|
+
When I call Foo # features/step_definitions/main_steps.rb:9
|
65
|
+
[ Cucover - Skipping clean scenario ]
|
66
|
+
|
67
|
+
Feature: Call Foo and Bar Together
|
68
|
+
|
69
|
+
Scenario: Call Foo and Bar # features/call_foo_and_bar_together.feature:3
|
70
|
+
When I call Foo # features/step_definitions/main_steps.rb:9
|
71
|
+
And I call Bar # features/step_definitions/main_steps.rb:9
|
72
|
+
|
73
|
+
2 scenarios (1 skipped, 1 passed)
|
74
|
+
3 steps (1 skipped, 2 passed)
|
75
|
+
|
76
|
+
"""
|
@@ -0,0 +1,108 @@
|
|
1
|
+
Feature: Lazy Run per Scenario
|
2
|
+
In order to work with a single feature
|
3
|
+
As a developer
|
4
|
+
I want to skip the scenarios that don't need to be run
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given I am using the simple example app
|
8
|
+
|
9
|
+
Scenario: Change nothing and run same feature again
|
10
|
+
Given I have run cucover -- features/call_foo_then_bar.feature
|
11
|
+
When I run cucover -- features/call_foo_then_bar.feature
|
12
|
+
Then it should pass with:
|
13
|
+
"""
|
14
|
+
Feature: Call Foo then Bar
|
15
|
+
|
16
|
+
Scenario: Call Foo # features/call_foo_then_bar.feature:3
|
17
|
+
When I call Foo # features/step_definitions/main_steps.rb:9
|
18
|
+
[ Cucover - Skipping clean scenario ]
|
19
|
+
|
20
|
+
Scenario: Call Bar # features/call_foo_then_bar.feature:6
|
21
|
+
When I call Bar # features/step_definitions/main_steps.rb:9
|
22
|
+
[ Cucover - Skipping clean scenario ]
|
23
|
+
|
24
|
+
2 scenarios (2 skipped)
|
25
|
+
2 steps (2 skipped)
|
26
|
+
|
27
|
+
"""
|
28
|
+
|
29
|
+
Scenario: Edit source file covered by only one scenario and run same feature again
|
30
|
+
Given I have run cucover -- features/call_foo_then_bar.feature
|
31
|
+
When I edit the source file lib/foo.rb
|
32
|
+
And I run cucover -- features/call_foo_then_bar.feature
|
33
|
+
Then it should pass with:
|
34
|
+
"""
|
35
|
+
Feature: Call Foo then Bar
|
36
|
+
|
37
|
+
Scenario: Call Foo # features/call_foo_then_bar.feature:3
|
38
|
+
When I call Foo # features/step_definitions/main_steps.rb:9
|
39
|
+
|
40
|
+
Scenario: Call Bar # features/call_foo_then_bar.feature:6
|
41
|
+
When I call Bar # features/step_definitions/main_steps.rb:9
|
42
|
+
[ Cucover - Skipping clean scenario ]
|
43
|
+
|
44
|
+
2 scenarios (1 skipped, 1 passed)
|
45
|
+
2 steps (1 skipped, 1 passed)
|
46
|
+
|
47
|
+
"""
|
48
|
+
|
49
|
+
Scenario: Run a feature with a background twice
|
50
|
+
Given I have run cucover -- features/call_foo_from_background_then_bar.feature
|
51
|
+
When I run cucover -- features/call_foo_from_background_then_bar.feature
|
52
|
+
Then it should pass with:
|
53
|
+
"""
|
54
|
+
Feature: Call Foo from Background then Bar
|
55
|
+
|
56
|
+
Background: # features/call_foo_from_background_then_bar.feature:3
|
57
|
+
Given I have called Foo # features/step_definitions/main_steps.rb:5
|
58
|
+
[ Cucover - Skipping clean scenario ]
|
59
|
+
|
60
|
+
Scenario: Call Bar # features/call_foo_from_background_then_bar.feature:6
|
61
|
+
When I call Bar # features/step_definitions/main_steps.rb:9
|
62
|
+
|
63
|
+
1 scenario (1 skipped)
|
64
|
+
2 steps (2 skipped)
|
65
|
+
|
66
|
+
"""
|
67
|
+
|
68
|
+
Scenario: Edit source file covered by the background of a feature
|
69
|
+
Given I have run cucover -- features/call_foo_from_background_then_bar.feature
|
70
|
+
When I edit the source file lib/foo.rb
|
71
|
+
And I run cucover -- features/call_foo_from_background_then_bar.feature
|
72
|
+
Then it should pass with:
|
73
|
+
"""
|
74
|
+
Feature: Call Foo from Background then Bar
|
75
|
+
|
76
|
+
Background: # features/call_foo_from_background_then_bar.feature:3
|
77
|
+
Given I have called Foo # features/step_definitions/main_steps.rb:5
|
78
|
+
|
79
|
+
Scenario: Call Bar # features/call_foo_from_background_then_bar.feature:6
|
80
|
+
When I call Bar # features/step_definitions/main_steps.rb:9
|
81
|
+
|
82
|
+
1 scenario (1 passed)
|
83
|
+
2 steps (2 passed)
|
84
|
+
|
85
|
+
"""
|
86
|
+
|
87
|
+
Scenario: Edit source file covered by a scenario with a background
|
88
|
+
Given I have run cucover -- features/call_foo_from_background_then_bar_then_baz.feature
|
89
|
+
When I edit the source file lib/bar.rb
|
90
|
+
And I run cucover -- features/call_foo_from_background_then_bar_then_baz.feature
|
91
|
+
Then it should pass with:
|
92
|
+
"""
|
93
|
+
Feature: Call Foo from Background then Bar then Baz
|
94
|
+
|
95
|
+
Background: # features/call_foo_from_background_then_bar_then_baz.feature:3
|
96
|
+
Given I have called Foo # features/step_definitions/main_steps.rb:5
|
97
|
+
|
98
|
+
Scenario: Call Bar # features/call_foo_from_background_then_bar_then_baz.feature:6
|
99
|
+
When I call Bar # features/step_definitions/main_steps.rb:9
|
100
|
+
|
101
|
+
Scenario: Call Baz # features/call_foo_from_background_then_bar_then_baz.feature:9
|
102
|
+
When I call Baz # features/step_definitions/main_steps.rb:9
|
103
|
+
[ Cucover - Skipping clean scenario ]
|
104
|
+
|
105
|
+
2 scenarios (1 skipped, 1 passed)
|
106
|
+
4 steps (2 skipped, 2 passed)
|
107
|
+
|
108
|
+
"""
|