test_data 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +45 -0
- data/CHANGELOG.md +8 -1
- data/Gemfile.lock +5 -3
- data/README.md +961 -17
- data/example/Gemfile +3 -0
- data/example/Gemfile.lock +32 -3
- data/example/README.md +2 -22
- data/example/config/credentials.yml.enc +2 -1
- data/example/config/database.yml +2 -0
- data/example/spec/rails_helper.rb +64 -0
- data/example/spec/requests/boops_spec.rb +21 -0
- data/example/spec/spec_helper.rb +94 -0
- data/example/test/factories.rb +4 -0
- data/example/test/integration/better_mode_switching_demo_test.rb +45 -0
- data/example/test/integration/boops_that_boop_boops_test.rb +17 -0
- data/example/test/integration/dont_dump_tables_test.rb +7 -0
- data/example/test/integration/load_rollback_truncate_test.rb +195 -0
- data/example/test/integration/mode_switching_demo_test.rb +48 -0
- data/example/test/integration/parallel_boops_with_fixtures_test.rb +14 -0
- data/example/test/integration/parallel_boops_without_fixtures_test.rb +13 -0
- data/example/test/integration/transaction_committing_boops_test.rb +25 -0
- data/example/test/test_helper.rb +3 -26
- data/lib/generators/test_data/database_yaml_generator.rb +1 -1
- data/lib/generators/test_data/environment_file_generator.rb +0 -14
- data/lib/generators/test_data/initializer_generator.rb +38 -0
- data/lib/generators/test_data/webpacker_yaml_generator.rb +1 -1
- data/lib/test_data.rb +5 -0
- data/lib/test_data/config.rb +25 -2
- data/lib/test_data/configurators.rb +1 -0
- data/lib/test_data/configurators/initializer.rb +25 -0
- data/lib/test_data/dumps_database.rb +31 -4
- data/lib/test_data/loads_database_dumps.rb +7 -7
- data/lib/test_data/log.rb +58 -0
- data/lib/test_data/rake.rb +7 -5
- data/lib/test_data/save_point.rb +34 -0
- data/lib/test_data/statistics.rb +26 -0
- data/lib/test_data/transactional_data_loader.rb +145 -32
- data/lib/test_data/verifies_dumps_are_loadable.rb +4 -4
- data/lib/test_data/version.rb +1 -1
- data/script/reset_example_app +17 -0
- data/script/test +54 -13
- metadata +19 -2
data/lib/test_data/rake.rb
CHANGED
@@ -24,9 +24,9 @@ desc "Verifies test_data environment looks good"
|
|
24
24
|
task "test_data:verify_config" do
|
25
25
|
config = TestData::VerifiesConfiguration.new.call
|
26
26
|
unless config.looks_good?
|
27
|
-
|
27
|
+
TestData.log.warn "The test_data gem is not configured correctly. Try 'rake test_data:configure'?\n"
|
28
28
|
config.problems.each do |problem|
|
29
|
-
|
29
|
+
TestData.log.warn " - #{problem}"
|
30
30
|
end
|
31
31
|
fail
|
32
32
|
end
|
@@ -54,11 +54,13 @@ desc "Initialize test_data Rails environment & configure database"
|
|
54
54
|
task "test_data:install" => ["test_data:configure", "test_data:initialize"]
|
55
55
|
|
56
56
|
desc "Dumps the interactive test_data database"
|
57
|
-
task "test_data:dump" => "test_data:verify_config" do
|
57
|
+
task "test_data:dump" => ["test_data:verify_config", :environment] do
|
58
|
+
next run_in_test_data_env("test_data:dump") if wrong_env?
|
59
|
+
|
58
60
|
TestData::DumpsDatabase.new.call
|
59
61
|
end
|
60
62
|
|
61
|
-
desc "
|
63
|
+
desc "Loads the schema and data SQL dumps into the test_data database"
|
62
64
|
task "test_data:load" => ["test_data:verify_config", :environment] do
|
63
65
|
next run_in_test_data_env("test_data:load") if wrong_env?
|
64
66
|
|
@@ -71,7 +73,7 @@ task "test_data:load" => ["test_data:verify_config", :environment] do
|
|
71
73
|
TestData::LoadsDatabaseDumps.new.call
|
72
74
|
|
73
75
|
if ActiveRecord::Base.connection.migration_context.needs_migration?
|
74
|
-
warn "
|
76
|
+
TestData.log.warn "There are pending migrations for database '#{TestData.config.database_name}'. To run them, run:\n\n RAILS_ENV=test_data bin/rake db:migrate\n\n"
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module TestData
|
2
|
+
class SavePoint
|
3
|
+
attr_reader :name, :transaction
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@transaction = connection.begin_transaction(joinable: false, _lazy: false)
|
8
|
+
end
|
9
|
+
|
10
|
+
def active?
|
11
|
+
!@transaction.state.finalized?
|
12
|
+
end
|
13
|
+
|
14
|
+
def rollback!
|
15
|
+
warn_if_not_rollbackable!
|
16
|
+
while active?
|
17
|
+
connection.rollback_transaction
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def connection
|
24
|
+
ActiveRecord::Base.connection
|
25
|
+
end
|
26
|
+
|
27
|
+
def warn_if_not_rollbackable!
|
28
|
+
return if active?
|
29
|
+
TestData.log.warn(
|
30
|
+
"Attempted to roll back transaction save point '#{name}', but its state was #{@transaction.state}"
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module TestData
|
2
|
+
def self.statistics
|
3
|
+
@statistics ||= Statistics.new
|
4
|
+
end
|
5
|
+
|
6
|
+
class Statistics
|
7
|
+
attr_reader :load_count, :truncate_count
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
reset
|
11
|
+
end
|
12
|
+
|
13
|
+
def count_load!
|
14
|
+
@load_count += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def count_truncate!
|
18
|
+
@truncate_count += 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset
|
22
|
+
@load_count = 0
|
23
|
+
@truncate_count = 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,77 +1,190 @@
|
|
1
|
-
require "fileutils"
|
2
|
-
|
3
1
|
module TestData
|
4
|
-
def self.
|
2
|
+
def self.load(transactions: true)
|
5
3
|
@transactional_data_loader ||= TransactionalDataLoader.new
|
6
|
-
@transactional_data_loader.
|
4
|
+
@transactional_data_loader.load(transactions: transactions)
|
7
5
|
end
|
8
6
|
|
9
|
-
def self.rollback(
|
10
|
-
|
11
|
-
|
7
|
+
def self.rollback(save_point_name = :after_data_load)
|
8
|
+
@transactional_data_loader ||= TransactionalDataLoader.new
|
9
|
+
case save_point_name
|
10
|
+
when :before_data_load
|
11
|
+
@transactional_data_loader.rollback_to_before_data_load
|
12
|
+
when :after_data_load
|
13
|
+
@transactional_data_loader.rollback_to_after_data_load
|
14
|
+
when :after_data_truncate
|
15
|
+
@transactional_data_loader.rollback_to_after_data_truncate
|
16
|
+
else
|
17
|
+
raise Error.new("No known save point named '#{save_point_name}'. Valid values are: [:before_data_load, :after_data_load, :after_data_truncate]")
|
18
|
+
end
|
12
19
|
end
|
13
20
|
|
14
|
-
|
15
|
-
|
21
|
+
def self.truncate(transactions: true)
|
22
|
+
@transactional_data_loader ||= TransactionalDataLoader.new
|
23
|
+
@transactional_data_loader.truncate(transactions: transactions)
|
24
|
+
end
|
16
25
|
|
26
|
+
class TransactionalDataLoader
|
17
27
|
def initialize
|
18
28
|
@config = TestData.config
|
29
|
+
@statistics = TestData.statistics
|
19
30
|
@save_points = []
|
20
|
-
@dump_count = 0
|
21
31
|
end
|
22
32
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
33
|
+
def load(transactions: true)
|
34
|
+
return execute_data_load unless transactions
|
35
|
+
ensure_after_load_save_point_is_active_if_data_is_loaded!
|
36
|
+
return rollback_to_after_data_load if save_point_active?(:after_data_load)
|
37
|
+
|
38
|
+
create_save_point(:before_data_load)
|
39
|
+
execute_data_load
|
40
|
+
record_ar_internal_metadata_that_test_data_is_loaded
|
41
|
+
create_save_point(:after_data_load)
|
42
|
+
end
|
43
|
+
|
44
|
+
def rollback_to_before_data_load
|
45
|
+
if save_point_active?(:before_data_load)
|
46
|
+
rollback_save_point(:before_data_load)
|
47
|
+
# No need to recreate the save point -- TestData.load will if called
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def rollback_to_after_data_load
|
52
|
+
if save_point_active?(:after_data_load)
|
53
|
+
rollback_save_point(:after_data_load)
|
28
54
|
create_save_point(:after_data_load)
|
29
55
|
end
|
30
56
|
end
|
31
57
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
58
|
+
def rollback_to_after_data_truncate
|
59
|
+
if save_point_active?(:after_data_truncate)
|
60
|
+
rollback_save_point(:after_data_truncate)
|
61
|
+
create_save_point(:after_data_truncate)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def truncate(transactions: true)
|
66
|
+
return execute_data_truncate unless transactions
|
67
|
+
ensure_after_load_save_point_is_active_if_data_is_loaded!
|
68
|
+
ensure_after_truncate_save_point_is_active_if_data_is_truncated!
|
69
|
+
return rollback_to_after_data_truncate if save_point_active?(:after_data_truncate)
|
70
|
+
|
71
|
+
if save_point_active?(:after_data_load)
|
72
|
+
# If a test that uses the test data runs before a test that starts by
|
73
|
+
# calling truncate, tables in the database that would NOT be truncated
|
74
|
+
# may have been changed. To avoid this category of test pollution, start
|
75
|
+
# the truncation by rolling back to the known clean point
|
76
|
+
rollback_to_after_data_load
|
77
|
+
else
|
78
|
+
# Seems silly loading data when the user asked us to truncate, but
|
79
|
+
# it's important that the state of the transaction stack matches the
|
80
|
+
# mental model we advertise, because any _other_ test in their suite
|
81
|
+
# should expect that the existence of :after_data_truncate save point
|
82
|
+
# implies that it's safe to rollback to the :after_data_load save
|
83
|
+
# point; since tests run in random order, it's likely to happen
|
84
|
+
TestData.log.debug("TestData.truncate was called, but data was not loaded. Loading data before truncate to preserve the documents transaction save point ordering")
|
85
|
+
load(transactions: true)
|
86
|
+
end
|
87
|
+
|
88
|
+
execute_data_truncate
|
89
|
+
record_ar_internal_metadata_that_test_data_is_truncated
|
90
|
+
create_save_point(:after_data_truncate)
|
35
91
|
end
|
36
92
|
|
37
93
|
private
|
38
94
|
|
39
|
-
def
|
95
|
+
def ensure_after_load_save_point_is_active_if_data_is_loaded!
|
96
|
+
if !save_point_active?(:after_data_load) && ar_internal_metadata_shows_test_data_is_loaded?
|
97
|
+
TestData.log.debug "Test data appears to be loaded, but the :after_data_load save point was rolled back (and not by this gem). Recreating the :after_data_load save point"
|
98
|
+
create_save_point(:after_data_load)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def record_ar_internal_metadata_that_test_data_is_loaded
|
103
|
+
if ar_internal_metadata_shows_test_data_is_loaded?
|
104
|
+
TestData.log.warn "Attempted to record that test data is loaded in ar_internal_metadata, but record already existed. Perhaps a previous test run committed your test data?"
|
105
|
+
else
|
106
|
+
ActiveRecord::InternalMetadata.create!(key: "test_data:loaded", value: "true")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def ar_internal_metadata_shows_test_data_is_loaded?
|
111
|
+
ActiveRecord::InternalMetadata.find_by(key: "test_data:loaded")&.value == "true"
|
112
|
+
end
|
113
|
+
|
114
|
+
def ensure_after_truncate_save_point_is_active_if_data_is_truncated!
|
115
|
+
if !save_point_active?(:after_data_truncate) && ar_internal_metadata_shows_test_data_is_truncated?
|
116
|
+
TestData.log.debug "Test data appears to be loaded, but the :after_data_truncate save point was rolled back (and not by this gem). Recreating the :after_data_truncate save point"
|
117
|
+
create_save_point(:after_data_truncate)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def record_ar_internal_metadata_that_test_data_is_truncated
|
122
|
+
if ar_internal_metadata_shows_test_data_is_truncated?
|
123
|
+
TestData.log.warn "Attempted to record that test data is truncated in ar_internal_metadata, but record already existed. Perhaps a previous test run committed the truncation of your test data?"
|
124
|
+
else
|
125
|
+
ActiveRecord::InternalMetadata.create!(key: "test_data:truncated", value: "true")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def ar_internal_metadata_shows_test_data_is_truncated?
|
130
|
+
ActiveRecord::InternalMetadata.find_by(key: "test_data:truncated")&.value == "true"
|
131
|
+
end
|
132
|
+
|
133
|
+
def execute_data_load
|
40
134
|
search_path = execute("show search_path").first["search_path"]
|
41
|
-
|
135
|
+
connection.disable_referential_integrity do
|
136
|
+
execute(File.read(@config.data_dump_full_path))
|
137
|
+
end
|
42
138
|
execute <<~SQL
|
43
139
|
select pg_catalog.set_config('search_path', '#{search_path}', false)
|
44
140
|
SQL
|
141
|
+
@statistics.count_load!
|
142
|
+
end
|
143
|
+
|
144
|
+
def execute_data_truncate
|
145
|
+
connection.disable_referential_integrity do
|
146
|
+
execute("TRUNCATE TABLE #{tables_to_truncate.map { |t| connection.quote_table_name(t) }.join(", ")}")
|
147
|
+
end
|
148
|
+
@statistics.count_truncate!
|
45
149
|
end
|
46
150
|
|
47
|
-
def
|
151
|
+
def tables_to_truncate
|
152
|
+
if @config.truncate_these_test_data_tables.present?
|
153
|
+
@config.truncate_these_test_data_tables
|
154
|
+
else
|
155
|
+
@tables_to_truncate ||= IO.foreach(@config.data_dump_path).grep(/^INSERT INTO/) { |line|
|
156
|
+
line.match(/^INSERT INTO ([^\s]+)/)&.captures&.first
|
157
|
+
}.compact.uniq
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def save_point_active?(name)
|
48
162
|
purge_closed_save_points!
|
49
|
-
|
163
|
+
!!@save_points.find { |sp| sp.name == name }&.active?
|
50
164
|
end
|
51
165
|
|
52
166
|
def create_save_point(name)
|
53
|
-
|
54
|
-
|
55
|
-
transaction: ActiveRecord::Base.connection.begin_transaction(joinable: false, _lazy: false)
|
56
|
-
)
|
57
|
-
@save_points << save_point
|
167
|
+
raise Error.new("Could not create test_data savepoint '#{name}', because it was already active!") if save_point_active?(name)
|
168
|
+
@save_points << SavePoint.new(name)
|
58
169
|
end
|
59
170
|
|
60
171
|
def rollback_save_point(name)
|
61
|
-
|
62
|
-
save_point.transaction.rollback
|
63
|
-
end
|
172
|
+
@save_points.find { |sp| sp.name == name }&.rollback!
|
64
173
|
purge_closed_save_points!
|
65
174
|
end
|
66
175
|
|
67
176
|
def purge_closed_save_points!
|
68
177
|
@save_points = @save_points.select { |save_point|
|
69
|
-
save_point.
|
178
|
+
save_point.active?
|
70
179
|
}
|
71
180
|
end
|
72
181
|
|
73
182
|
def execute(sql)
|
74
|
-
|
183
|
+
connection.execute(sql)
|
184
|
+
end
|
185
|
+
|
186
|
+
def connection
|
187
|
+
ActiveRecord::Base.connection
|
75
188
|
end
|
76
189
|
end
|
77
190
|
end
|
@@ -8,22 +8,22 @@ module TestData
|
|
8
8
|
def call(quiet: false)
|
9
9
|
schema_dump_looks_good = Pathname.new(@config.schema_dump_full_path).readable?
|
10
10
|
if !quiet && !schema_dump_looks_good
|
11
|
-
warn "Warning: Database schema dump '#{@config.schema_dump_path}' not readable"
|
11
|
+
log.warn "Warning: Database schema dump '#{@config.schema_dump_path}' not readable"
|
12
12
|
end
|
13
13
|
|
14
14
|
data_dump_looks_good = Pathname.new(@config.data_dump_full_path).readable?
|
15
15
|
if !quiet && !data_dump_looks_good
|
16
|
-
warn "Warning: Database data dump '#{@config.data_dump_path}' not readable"
|
16
|
+
log.warn "Warning: Database data dump '#{@config.data_dump_path}' not readable"
|
17
17
|
end
|
18
18
|
|
19
19
|
non_test_data_dump_looks_good = Pathname.new(@config.non_test_data_dump_full_path).readable?
|
20
20
|
if !quiet && !non_test_data_dump_looks_good
|
21
|
-
warn "Warning: Database non-test data dump '#{@config.non_test_data_dump_path}' not readable"
|
21
|
+
log.warn "Warning: Database non-test data dump '#{@config.non_test_data_dump_path}' not readable"
|
22
22
|
end
|
23
23
|
|
24
24
|
database_empty = @detects_database_emptiness.empty?
|
25
25
|
unless quiet || database_empty
|
26
|
-
warn "Warning: Database '#{@config.database_name}' is not empty"
|
26
|
+
log.warn "Warning: Database '#{@config.database_name}' is not empty"
|
27
27
|
end
|
28
28
|
|
29
29
|
[
|
data/lib/test_data/version.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
PS4='[script/test:${LINENO}] $ '
|
4
|
+
set -euo pipefail
|
5
|
+
set -x
|
6
|
+
|
7
|
+
cd example
|
8
|
+
|
9
|
+
# Reset database:
|
10
|
+
bin/rake db:drop
|
11
|
+
dropdb example_test_data 2>/dev/null || true
|
12
|
+
|
13
|
+
# Reset files:
|
14
|
+
git checkout app/models/boop.rb
|
15
|
+
git checkout config/database.yml
|
16
|
+
git checkout db/schema.rb
|
17
|
+
git clean -xdf .
|
data/script/test
CHANGED
@@ -1,25 +1,30 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
2
|
|
3
3
|
PS4='[script/test:${LINENO}] $ '
|
4
|
-
set -
|
4
|
+
set -euo pipefail
|
5
5
|
set -x
|
6
6
|
|
7
|
-
#
|
7
|
+
# Install deps and make sure gem passes its own rake
|
8
8
|
bundle
|
9
9
|
bundle exec rake
|
10
|
-
|
11
|
-
# Exercise the example app
|
12
10
|
cd example
|
13
11
|
bundle
|
14
12
|
|
15
13
|
# Avoid test pollution by clearing out any initial state that might be lingering
|
16
|
-
|
17
|
-
|
14
|
+
cd ..
|
15
|
+
./script/reset_example_app
|
16
|
+
|
17
|
+
# Exercise the example app
|
18
|
+
cd example
|
19
|
+
bin/rake db:setup
|
18
20
|
|
19
21
|
# Test basic initial usage
|
20
22
|
bin/rake test_data:install
|
21
23
|
bin/rake test_data:dump
|
22
24
|
bin/rails test test/integration/basic_boops_test.rb
|
25
|
+
bundle exec rspec spec/requests/boops_spec.rb
|
26
|
+
bin/rails test test/integration/mode_switching_demo_test.rb
|
27
|
+
bin/rails test test/integration/better_mode_switching_demo_test.rb
|
23
28
|
bin/rails test test/integration/parallel_boops_with_fixtures_test.rb
|
24
29
|
bin/rails test test/integration/parallel_boops_without_fixtures_test.rb
|
25
30
|
|
@@ -38,12 +43,48 @@ RAILS_ENV=test_data bin/rake db:migrate
|
|
38
43
|
bin/rake test_data:dump
|
39
44
|
bin/rails test test/integration/migrated_boops_test.rb
|
40
45
|
|
41
|
-
#
|
46
|
+
# Run a test that commits test data thru to the database
|
47
|
+
bin/rails test test/integration/transaction_committing_boops_test.rb
|
48
|
+
|
49
|
+
# Add a second migration, this time without wiping the test_data db and with a table we want to ignore
|
50
|
+
cp ../test/fixtures/20210423114916_add_table_we_want_to_ignore.rb db/migrate
|
51
|
+
cp ../test/fixtures/chatty_audit_log.rb app/models
|
52
|
+
bin/rake db:migrate
|
53
|
+
RAILS_ENV=test_data bin/rake db:migrate
|
54
|
+
RAILS_ENV=test_data rails runner "50.times { ChattyAuditLog.create!(message: 'none of this matters') }"
|
55
|
+
# Gsub config file and uncomment + add table to excluded table list
|
56
|
+
ruby -e '
|
57
|
+
path = "config/initializers/test_data.rb"
|
58
|
+
IO.write(path, File.open(path) { |f|
|
59
|
+
f.read.gsub("# config.dont_dump_these_tables = []", "config.dont_dump_these_tables = [\"chatty_audit_logs\"]")
|
60
|
+
})
|
61
|
+
'
|
62
|
+
bin/rake test_data:dump
|
63
|
+
if grep -q "INSERT INTO public.chatty_audit_logs" "test/support/test_data/data.sql"; then
|
64
|
+
echo "Dump contained excluded table 'chatty_audit_logs'"
|
65
|
+
exit 1
|
66
|
+
fi
|
67
|
+
bin/rake db:test:prepare
|
68
|
+
bin/rails test test/integration/dont_dump_tables_test.rb
|
69
|
+
bin/rails test test/integration/load_rollback_truncate_test.rb
|
42
70
|
|
43
|
-
#
|
71
|
+
# Test circular FK constraints
|
72
|
+
cp ../test/fixtures/20210423190737_add_foreign_keys.rb db/migrate/
|
73
|
+
cp ../test/fixtures/boop_with_other_boops.rb app/models/boop.rb
|
74
|
+
RAILS_ENV=test_data bin/rake db:migrate
|
75
|
+
bin/rake test_data:dump
|
76
|
+
bin/rake db:migrate
|
77
|
+
bin/rake db:test:prepare
|
78
|
+
bin/rails test test/integration/boops_that_boop_boops_test.rb
|
79
|
+
|
80
|
+
# Make sure it loads cleanly again
|
44
81
|
bin/rake test_data:drop_database
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
82
|
+
if [ ! bin/rake test_data:load | grep -q "ERROR" ]; then
|
83
|
+
echo "Running test_data:load after adding FK constraints led to errors"
|
84
|
+
exit 1
|
85
|
+
fi
|
86
|
+
bin/rails test test/integration/boops_that_boop_boops_test.rb
|
87
|
+
|
88
|
+
# Cleanup
|
89
|
+
cd ..
|
90
|
+
./script/reset_example_app
|