snowglobe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a7cb4e42cb80539ad12a929b77cdbc345594797a830e1c84afc335c6f70db7d
4
+ data.tar.gz: 57224881549f30823d7e22b1c804e1d43a31cde9feadb159f437e09ac887e16b
5
+ SHA512:
6
+ metadata.gz: 154526eb1114b432086be4722e2b8e9e4d761a3850d60ea5e75465aca291b96722f1c450666496880f81f6f81369ae6e9cb5352705943645c566969218aab794
7
+ data.tar.gz: a260aa8b610ad5d94019b28947814ed728b5d7d2b3a8007ac62ed2ccb5af77f40391a91a3d2e730115f5f91a250e48af4ef56de78ee21242580ebe5144592ce1
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,132 @@
1
+ ---
2
+ AllCops:
3
+ Exclude:
4
+ - db/schema.rb
5
+ TargetRubyVersion: 2.5
6
+ Layout/AlignParameters:
7
+ EnforcedStyle: with_fixed_indentation
8
+ Layout/ClassStructure:
9
+ Enabled: true
10
+ Layout/DotPosition:
11
+ EnforcedStyle: trailing
12
+ Layout/InitialIndentation:
13
+ Enabled: false
14
+ Layout/IndentArray:
15
+ EnforcedStyle: "consistent"
16
+ Layout/IndentHash:
17
+ EnforcedStyle: "consistent"
18
+ Layout/IndentHeredoc:
19
+ Enabled: false
20
+ Layout/MultilineOperationIndentation:
21
+ EnforcedStyle: indented
22
+ Layout/MultilineMethodCallIndentation:
23
+ EnforcedStyle: indented
24
+ Layout/SpaceInLambdaLiteral:
25
+ Enabled: false
26
+ Layout/SpaceInsideStringInterpolation:
27
+ Enabled: false
28
+ Lint/AssignmentInCondition:
29
+ Enabled: false
30
+ Lint/HandleExceptions:
31
+ Enabled: false
32
+ Lint/RequireParentheses:
33
+ Enabled: false
34
+ Metrics/AbcSize:
35
+ Max: 32
36
+ Metrics/BlockLength:
37
+ ExcludedMethods:
38
+ - describe
39
+ - context
40
+ - it
41
+ - colored
42
+ Metrics/ClassLength:
43
+ Enabled: false
44
+ Metrics/LineLength:
45
+ Max: 80
46
+ IgnoredPatterns:
47
+ - "^\\s*describe"
48
+ - "^\\s*context"
49
+ - "^\\s*it"
50
+ Metrics/MethodLength:
51
+ Enabled: false
52
+ Metrics/ModuleLength:
53
+ Enabled: false
54
+ Metrics/ParameterLists:
55
+ CountKeywordArgs: false
56
+ Naming/MemoizedInstanceVariableName:
57
+ EnforcedStyleForLeadingUnderscores: required
58
+ Naming/PredicateName:
59
+ NamePrefixBlacklist:
60
+ - is_
61
+ Exclude:
62
+ - spec/**/*
63
+ Naming/UncommunicativeMethodParamName:
64
+ Enabled: false
65
+ Style/Alias:
66
+ Enabled: false
67
+ Style/BracesAroundHashParameters:
68
+ EnforcedStyle: context_dependent
69
+ Style/CollectionMethods:
70
+ Enabled: true
71
+ Style/Documentation:
72
+ Enabled: false
73
+ Style/EachWithObject:
74
+ Enabled: false
75
+ Style/EmptyMethod:
76
+ EnforcedStyle: expanded
77
+ Style/FormatString:
78
+ EnforcedStyle: percent
79
+ Style/FrozenStringLiteralComment:
80
+ Enabled: false
81
+ Style/GuardClause:
82
+ Enabled: false
83
+ Style/IfUnlessModifier:
84
+ Enabled: false
85
+ Style/Lambda:
86
+ Enabled: false
87
+ Style/LineEndConcatenation:
88
+ Enabled: false
89
+ Style/MethodCalledOnDoEndBlock:
90
+ Enabled: true
91
+ Style/MultilineBlockChain:
92
+ Enabled: false
93
+ Style/NegatedIf:
94
+ Enabled: false
95
+ Style/NegatedWhile:
96
+ Enabled: false
97
+ Style/OneLineConditional:
98
+ Enabled: false
99
+ Style/PercentLiteralDelimiters:
100
+ Enabled: false
101
+ Style/PreferredHashMethods:
102
+ Enabled: false
103
+ Style/RaiseArgs:
104
+ Enabled: false
105
+ Style/SafeNavigation:
106
+ Enabled: false
107
+ Style/Send:
108
+ Enabled: true
109
+ Style/SignalException:
110
+ Enabled: false
111
+ Style/SpecialGlobalVars:
112
+ Enabled: false
113
+ Style/StringLiterals:
114
+ EnforcedStyle: double_quotes
115
+ Style/StringLiteralsInInterpolation:
116
+ EnforcedStyle: double_quotes
117
+ Style/StringMethods:
118
+ Enabled: true
119
+ Style/SymbolArray:
120
+ Enabled: false
121
+ Style/TrailingCommaInArguments:
122
+ EnforcedStyleForMultiline: comma
123
+ Style/TrailingCommaInArrayLiteral:
124
+ EnforcedStyleForMultiline: comma
125
+ Style/TrailingCommaInHashLiteral:
126
+ EnforcedStyleForMultiline: comma
127
+ Style/UnneededCondition:
128
+ Enabled: false
129
+ Style/WhileUntilModifier:
130
+ Enabled: false
131
+ Style/WordArray:
132
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.1
7
+ before_install: gem install bundler -v 2.0.1
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ snowglobe (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (10.5.0)
11
+ rspec (3.8.0)
12
+ rspec-core (~> 3.8.0)
13
+ rspec-expectations (~> 3.8.0)
14
+ rspec-mocks (~> 3.8.0)
15
+ rspec-core (3.8.0)
16
+ rspec-support (~> 3.8.0)
17
+ rspec-expectations (3.8.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.8.0)
20
+ rspec-mocks (3.8.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.8.0)
23
+ rspec-support (3.8.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 2.0)
30
+ rake (~> 10.0)
31
+ rspec (~> 3.0)
32
+ snowglobe!
33
+
34
+ BUNDLED WITH
35
+ 2.0.1
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Elliot Winkler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Snowglobe
2
+
3
+ Snowglobe is a gem that helps erect and destroy Rails applications for use in
4
+ tests.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your project's Gemfile:
9
+
10
+ ``` ruby
11
+ gem 'snowglobe'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ ``` bash
17
+ bundle
18
+ ```
19
+
20
+ Alternatively, install it yourself as:
21
+
22
+ ``` bash
23
+ gem install snowglobe
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ (To be filled in later)
29
+
30
+ ## Developing
31
+
32
+ * `bin/setup` to get started
33
+ * `bundle exec rake release` to release a new version
34
+
35
+ ## Author/License
36
+
37
+ Snowglobe is © 2019 Elliot Winkler (<elliot.winkler@gmail.com>) and is released
38
+ under the [MIT license](LICENSE).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "snowglobe"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,93 @@
1
+ require_relative "filesystem"
2
+ require_relative "command_runner"
3
+ require_relative "gem_version"
4
+
5
+ module Snowglobe
6
+ class Bundle
7
+ def initialize(fs:, command_runner:)
8
+ @fs = fs
9
+ @command_runner = command_runner
10
+ @already_updating = false
11
+ end
12
+
13
+ def add_gem(gem, *args)
14
+ updating do
15
+ options = args.last.is_a?(Hash) ? args.pop : {}
16
+ version = args.shift
17
+ line = assemble_gem_line(gem, version, options)
18
+ fs.append_to_file("Gemfile", line)
19
+ end
20
+ end
21
+
22
+ def remove_gem(gem)
23
+ updating do
24
+ fs.comment_lines_matching_in_file("Gemfile", /^[ ]*gem ("|')#{gem}\1/)
25
+ end
26
+ end
27
+
28
+ def updating
29
+ if already_updating?
30
+ yield self
31
+ return
32
+ end
33
+
34
+ @already_updating = true
35
+
36
+ yield self
37
+
38
+ @already_updating = false
39
+
40
+ install_gems
41
+ end
42
+
43
+ def install_gems
44
+ command_runner.run!("bundle install --local") do |runner|
45
+ runner.retries = 5
46
+ end
47
+ end
48
+
49
+ def version_of(gem)
50
+ GemVersion.new(Bundler.definition.specs[gem][0].version)
51
+ end
52
+
53
+ def includes?(gem)
54
+ Bundler.definition.dependencies.any? do |dependency|
55
+ dependency.name == gem
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :fs, :command_runner
62
+
63
+ def already_updating?
64
+ @already_updating
65
+ end
66
+
67
+ def assemble_gem_line(gem, version, options)
68
+ formatted_options = options.
69
+ map { |key, value| "#{key}: #{formatted_value(value)}" }.
70
+ join(", ")
71
+
72
+ line = %(gem '#{gem}')
73
+
74
+ if version
75
+ line << %(, '#{version}')
76
+ end
77
+
78
+ if options.any?
79
+ line << %(, #{formatted_options})
80
+ end
81
+
82
+ line << "\n"
83
+ end
84
+
85
+ def formatted_value(value)
86
+ if value.is_a?(Pathname)
87
+ value.to_s.inspect
88
+ else
89
+ value.inspect
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,232 @@
1
+ require "forwardable"
2
+ require "timeout"
3
+ require "shellwords"
4
+
5
+ module Snowglobe
6
+ class CommandRunner
7
+ extend Forwardable
8
+
9
+ TimeoutError = Class.new(StandardError)
10
+
11
+ def self.run(*args, **options, &block)
12
+ new(*args, **options, &block).tap(&:call)
13
+ end
14
+
15
+ def self.run!(*args, **options, &block)
16
+ run(*args, run_successfully: true, **options, &block)
17
+ end
18
+
19
+ attr_reader :status, :options, :env
20
+ attr_accessor :command_prefix, :run_quickly, :run_successfully, :retries,
21
+ :timeout
22
+
23
+ def initialize(
24
+ *args,
25
+ env: {},
26
+ directory: Dir.pwd,
27
+ run_successfully: false,
28
+ **options
29
+ )
30
+ @reader, @writer = IO.pipe
31
+ @options = options.merge(err: [:child, :out], out: writer)
32
+
33
+ @args = args
34
+ @env = normalize_env(env)
35
+ self.directory = directory
36
+ @run_successfully = run_successfully
37
+
38
+ @wrapper = ->(block) { block.call }
39
+ @command_prefix = ""
40
+ @run_quickly = false
41
+ @retries = 1
42
+ @num_times_run = 0
43
+ @timeout = 20
44
+
45
+ yield self if block_given?
46
+ end
47
+
48
+ def directory
49
+ options[:chdir]
50
+ end
51
+
52
+ def directory=(directory)
53
+ if directory.nil?
54
+ raise ArgumentError, "Must provide a directory"
55
+ end
56
+
57
+ options[:chdir] = directory
58
+ end
59
+
60
+ def around_command(&block)
61
+ @wrapper = block
62
+ end
63
+
64
+ def formatted_command
65
+ [formatted_env, Shellwords.join(command)].
66
+ reject(&:empty?).
67
+ join(" ")
68
+ end
69
+
70
+ def call
71
+ possibly_retrying do
72
+ possibly_running_quickly do
73
+ run_with_debugging
74
+
75
+ if run_successfully && !success?
76
+ fail!
77
+ end
78
+ end
79
+ end
80
+
81
+ self
82
+ end
83
+
84
+ def stop
85
+ unless writer.closed?
86
+ writer.close
87
+ end
88
+ end
89
+
90
+ def output
91
+ @_output ||= begin
92
+ stop
93
+ without_colors(reader.read)
94
+ end
95
+ end
96
+
97
+ def elided_output
98
+ lines = output.split(/\n/)
99
+ new_lines = lines[0..4]
100
+
101
+ if lines.size > 10
102
+ new_lines << "(...#{lines.size - 10} more lines...)"
103
+ end
104
+
105
+ new_lines << lines[-5..-1]
106
+ new_lines.join("\n")
107
+ end
108
+
109
+ def_delegators :status, :success?
110
+
111
+ def exit_status
112
+ status.exitstatus
113
+ end
114
+
115
+ def fail!
116
+ raise <<-MESSAGE
117
+ Command #{formatted_command.inspect} exited with status #{exit_status}.
118
+ Output:
119
+ #{divider("START") + output + divider("END")}
120
+ MESSAGE
121
+ end
122
+
123
+ def has_output?(expected_output)
124
+ if expected_output.is_a?(Regexp)
125
+ output =~ expected_output
126
+ else
127
+ output.include?(expected_output)
128
+ end
129
+ end
130
+
131
+ protected
132
+
133
+ attr_reader :args, :reader, :writer, :wrapper
134
+
135
+ private
136
+
137
+ def normalize_env(env)
138
+ env.reduce({}) do |hash, (key, value)|
139
+ hash.merge(key.to_s => value)
140
+ end
141
+ end
142
+
143
+ def command
144
+ ([command_prefix] + args).flatten.flat_map do |word|
145
+ Shellwords.split(word)
146
+ end
147
+ end
148
+
149
+ def formatted_env
150
+ env.map { |key, value| "#{key}=#{value.inspect}" }.join(" ")
151
+ end
152
+
153
+ def run
154
+ pid = spawn(env, *command, options)
155
+ Process.waitpid(pid)
156
+ @status = $?
157
+ end
158
+
159
+ def run_with_wrapper
160
+ wrapper.call(method(:run))
161
+ end
162
+
163
+ def run_with_debugging
164
+ debug { "\n\e[33mChanging to directory:\e[0m #{directory}" }
165
+ debug { "\e[32mRunning command:\e[0m #{formatted_command}" }
166
+
167
+ run_with_wrapper
168
+
169
+ debug { "\n" + divider("START") + output + divider("END") }
170
+ end
171
+
172
+ def possibly_running_quickly(&block)
173
+ if run_quickly
174
+ begin
175
+ Timeout.timeout(timeout, &block)
176
+ rescue Timeout::Error
177
+ stop
178
+
179
+ message =
180
+ "Command timed out after #{timeout} seconds: " +
181
+ "#{formatted_command}\n" +
182
+ "Output:\n" +
183
+ output
184
+
185
+ raise TimeoutError, message
186
+ end
187
+ else
188
+ yield
189
+ end
190
+ end
191
+
192
+ def possibly_retrying
193
+ @num_times_run += 1
194
+ yield
195
+ rescue StandardError => error
196
+ debug { "#{error.class}: #{error.message}" }
197
+
198
+ if @num_times_run < @retries
199
+ sleep @num_times_run
200
+ retry
201
+ else
202
+ raise error
203
+ end
204
+ end
205
+
206
+ def divider(title = "")
207
+ total_length = 72
208
+ start_length = 3
209
+
210
+ string = ""
211
+ string << ("-" * start_length)
212
+ string << title
213
+ string << "-" * (total_length - start_length - title.length)
214
+ string << "\n"
215
+ string
216
+ end
217
+
218
+ def without_colors(string)
219
+ string.gsub(/\e\[\d+(?:;\d+)?m(.+?)\e\[0m/, '\1')
220
+ end
221
+
222
+ def debugging_enabled?
223
+ ENV["DEBUG_COMMANDS"] == "1"
224
+ end
225
+
226
+ def debug
227
+ if debugging_enabled?
228
+ puts yield
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,43 @@
1
+ module Snowglobe
2
+ class Configuration
3
+ attr_accessor :root_directory
4
+ attr_accessor :temporary_directory
5
+ attr_writer :project_name
6
+
7
+ def initialize
8
+ self.temporary_directory = Pathname.new("/tmp/snowglobe")
9
+ @project_name = nil
10
+ @database_name = nil
11
+ end
12
+
13
+ def project_name
14
+ if @project_name
15
+ "#{@project_name}-test-app"
16
+ else
17
+ raise NotConfiguredError.new(<<~EXAMPLE)
18
+ Snowglobe.configure do |config|
19
+ config.project_name = "your_project_name"
20
+ end
21
+ EXAMPLE
22
+ end
23
+ end
24
+
25
+ def database_name
26
+ @database_name || project_name
27
+ end
28
+
29
+ def update!
30
+ yield self
31
+ end
32
+
33
+ class NotConfiguredError < StandardError
34
+ def initialize(example)
35
+ super(<<~MESSAGE)
36
+ You need to configure Snowglobe before you can use it! For example:
37
+
38
+ #{example}
39
+ MESSAGE
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ require_relative "database_configuration"
2
+
3
+ module Snowglobe
4
+ class Database
5
+ ADAPTER_NAME = ENV.fetch("DATABASE_ADAPTER", "sqlite3").to_sym
6
+
7
+ attr_reader :config
8
+
9
+ def initialize
10
+ @config = DatabaseConfiguration.for(
11
+ Snowglobe.configuration.database_name,
12
+ ADAPTER_NAME,
13
+ )
14
+ end
15
+
16
+ def name
17
+ config.database
18
+ end
19
+
20
+ def adapter_name
21
+ config.adapter
22
+ end
23
+
24
+ def adapter_class
25
+ config.adapter_class
26
+ end
27
+ end
28
+ end