heroku_hatchet 7.1.3 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 457db77bc6c69de2ef9179c0b3027ab3c8036b2e639dc924bbb3d4c6903b32b5
4
- data.tar.gz: c42cf1ceb1a679f1f0dfdff99c60e6ba1b7cc442d5accd0182ac8b44b2d8add1
3
+ metadata.gz: 2dd4a304828fca43576fed5a71b7be305142c3220c7dd020bff44b17a4a070a0
4
+ data.tar.gz: c0dee0a8ffb794f10a3dbb96beeafa26dd5f95b489eb9638a40bf408dce44c11
5
5
  SHA512:
6
- metadata.gz: 4f7d83b39c17fb2700d4b3ddca889df1fd55a884017d09d72b178870d556b1948753d491fbeb02c9a35aee92c3de8d49d4c4aca6c2fd164ddfabe4f3bfe54168
7
- data.tar.gz: 78e2a70ce5b00b69e1c98da9a36f691978e043db9e58976947011a3178e8bf6c63520dca01a471c58fdc24e5da7726408704bb0d46f2f0e8ed31ce61fe4ec5c0
6
+ metadata.gz: f6fa0cef6167e9b4985b08f01e42f33410632d4dfb083551d2641e7009745a079044f606cc4456ed8b8f0dfb0ec087b9b3197d1dabb9a0c41cd7f96ee978e22c
7
+ data.tar.gz: 50689c87372d4df2fc7d354ccc0f6f870285283869a190f3272f76d0d238e2d3cd13ca485ca6436916a6976c847fbd2be057fbe4e99ed1aa8ac97abdfef9eeb9
@@ -1,7 +1,8 @@
1
1
  name: Check Changelog
2
2
 
3
- on: [pull_request]
4
-
3
+ on:
4
+ pull_request:
5
+ types: [opened, reopened, edited, synchronize]
5
6
  jobs:
6
7
  build:
7
8
  runs-on: ubuntu-latest
@@ -1,5 +1,10 @@
1
1
  ## HEAD
2
2
 
3
+ ## 7.1.4
4
+
5
+ - App#setup! no longer modifies files on disk. (https://github.com/heroku/hatchet/pull/125)
6
+ - Add `$ hatchet init` command for bootstrapping new projects (https://github.com/heroku/hatchet/pull/123)
7
+
3
8
  ## 7.1.3
4
9
 
5
10
  - Important!! Fix branch name detection on CircleCI (https://github.com/heroku/hatchet/pull/124)
data/README.md CHANGED
@@ -57,6 +57,8 @@ In addition to speed, Hatchet provides isolation. Suppose you're executing `bin/
57
57
 
58
58
  ## Quicklinks
59
59
 
60
+ - Getting started
61
+ - [Add hatchet tests to a existing buildpack](#hatchet-init)
60
62
  - Concepts
61
63
  - [Tell Hatchet how to find your buildpack](#specify-buildpack)
62
64
  - [Give Hatchet some example apps to deploy](#example-apps)
@@ -78,6 +80,45 @@ In addition to speed, Hatchet provides isolation. Suppose you're executing `bin/
78
80
  - [Introduction to the Rspec testing framework for non-rubyists](#basic-rspec)
79
81
  - [Introduction to Ruby for non-rubyists](#basic-ruby)
80
82
 
83
+ ## Getting Started
84
+
85
+ ### Hatchet Init
86
+
87
+ If you're working in a project that does not already have hatchet tests you can run this command to get started quickly:
88
+
89
+ Make sure you're in directory that contains your buildpack and run:
90
+
91
+ ```
92
+ $ gem install heroku_hatchet
93
+ $ hatchet init
94
+ ```
95
+
96
+ This will bootstrap your project with the necessarry files to test your buildpack. Including but not limited to:
97
+
98
+ - Gemfile
99
+ - hatchet.json
100
+ - spec/spec_helper.rb
101
+ - spec/hatchet/buildpack_spec.rb
102
+ - .circleci/config.yml
103
+ - .github/dependabot.yml
104
+ - .gitignore
105
+
106
+ Once this executes successfully then you can run your tests with:
107
+
108
+ ```
109
+ $ bundle exec rspec
110
+ ```
111
+
112
+ > Note: You'll need to update the `buildpack_spec.rb` file to remove the exception
113
+
114
+ You can also focus a specific file or test by providing a path and line number:
115
+
116
+ ```
117
+ $ bundle exec rspec spec/hatchet/buildpack_spec:5
118
+ ```
119
+
120
+ Keep reading to find out more about how hatchet works.
121
+
81
122
  ## Concepts
82
123
 
83
124
  ### Specify buildpack
@@ -89,7 +130,7 @@ ENV["HATCHET_BUILDPACK_BASE"] = "https://github.com/path-to-your/buildpack"
89
130
  require 'hatchet'`
90
131
  ```
91
132
 
92
- If you do not specify `HATCHET_BUILDPACK_URL` the default Ruby buildpack will be used. If you do not specify a `HATCHET_BUILDPACK_BRANCH` the current branch you are on will be used. This is how the Ruby buildpack runs tests on branches on CI (by leaving `HATCHET_BUILDPACK_BRANCH` blank).
133
+ If you do not specify `HATCHET_BUILDPACK_BASE` the default Ruby buildpack will be used. If you do not specify a `HATCHET_BUILDPACK_BRANCH` the current branch you are on will be used. This is how the Ruby buildpack runs tests on branches on CI (by leaving `HATCHET_BUILDPACK_BRANCH` blank).
93
134
 
94
135
  The workflow generally looks like this:
95
136
 
@@ -156,7 +197,7 @@ You can reference one of these applications in your test by using it's git name:
156
197
  Hatchet::Runner.new('no_lockfile')
157
198
  ```
158
199
 
159
- If you have conflicting names, use full paths like `Hatchet::RUnner.new("sharpstone/no_lockfile")`.
200
+ If you have conflicting names, use full paths like `Hatchet::Runner.new("sharpstone/no_lockfile")`.
160
201
 
161
202
  When you run `hatchet install` it will lock all the Repos to a specific commit. This is done so that if a repo changes upstream that introduces an error the test suite won't automatically pick it up. For example in https://github.com/sharpstone/lock_fail/commit/e61ba47043fbae131abb74fd74added7e6e504df an error is added, but this will only cause a failure if your project intentionally locks to commit `e61ba47043fbae131abb74fd74added7e6e504df` or later.
162
203
 
@@ -15,8 +15,14 @@ require 'thor'
15
15
  require 'threaded'
16
16
  require 'date'
17
17
  require 'yaml'
18
+ require 'pathname'
18
19
 
19
20
  class HatchetCLI < Thor
21
+ desc "init", "bootstraps a project with minimal files required to add hatchet tests"
22
+ define_method("init") do
23
+ Hatchet::InitProject.new.call
24
+ end
25
+
20
26
  desc "ci:install_heroku", "installs the `heroku` cli"
21
27
  define_method("ci:install_heroku") do
22
28
  if `which heroku` && $?.success?
@@ -18,6 +18,7 @@ require 'hatchet/anvil_app'
18
18
  require 'hatchet/git_app'
19
19
  require 'hatchet/config'
20
20
  require 'hatchet/api_rate_limit'
21
+ require 'hatchet/init_project'
21
22
 
22
23
  module Hatchet
23
24
  RETRIES = Integer(ENV['HATCHET_RETRIES'] || 1)
@@ -297,19 +297,27 @@ module Hatchet
297
297
  def setup!
298
298
  return self if @app_is_setup
299
299
  puts "Hatchet setup: #{name.inspect} for #{repo_name.inspect}"
300
- create_git_repo! unless is_git_repo?
301
300
  create_app
302
301
  set_labs!
303
302
  buildpack_list = @buildpacks.map { |pack| { buildpack: pack } }
304
303
  api_rate_limit.call.buildpack_installation.update(name, updates: buildpack_list)
305
304
  set_config @app_config
306
305
 
307
- call_before_deploy
308
306
  @app_is_setup = true
309
307
  self
310
308
  end
311
309
  alias :setup :setup!
312
310
 
311
+ private def in_dir_setup!
312
+ setup!
313
+ raise "Error you're in #{Dir.pwd} and might accidentally modify your disk contents" unless @already_in_dir
314
+ @in_dir_setup ||= begin
315
+ create_git_repo! unless is_git_repo?
316
+ call_before_deploy
317
+ true
318
+ end
319
+ end
320
+
313
321
  def before_deploy(&block)
314
322
  raise "block required" unless block
315
323
  @before_deploy = block
@@ -379,7 +387,7 @@ module Hatchet
379
387
 
380
388
  def deploy(&block)
381
389
  in_directory do
382
- self.setup!
390
+ in_dir_setup!
383
391
  self.push_with_retry!
384
392
  block.call(self, api_rate_limit.call, output) if block_given?
385
393
  end
@@ -434,7 +442,7 @@ module Hatchet
434
442
  # that would, with bad timing, mean our test run info poll in wait! would 403, and/or the delete_pipeline at the end
435
443
  # that's why we create an app explictly (or maybe it already exists), and then associate it with with the pipeline
436
444
  # the app will be auto cleaned up later
437
- self.setup!
445
+ in_dir_setup!
438
446
  max_retries_count.times.retry do
439
447
  couple_pipeline(@name, @pipeline_id)
440
448
  end
@@ -0,0 +1,86 @@
1
+ require 'thor'
2
+ require 'yaml'
3
+
4
+ module Hatchet
5
+ # Bootstraps a project with files for running hatchet tests
6
+ #
7
+ # Hatchet::InitProject.new.call
8
+ #
9
+ # puts File.exist?("spec/spec_helper.rb") # => true
10
+ # puts File.exist?("") # => true
11
+ class InitProject
12
+ def initialize(dir: ".", io: STDOUT)
13
+
14
+ @target_dir = Pathname.new(dir)
15
+ raise "Must run in a directory with a buildpack, #{@target_dir} has no bin/ directory" unless @target_dir.join("bin").directory?
16
+
17
+ @template_dir = Pathname.new(__dir__).join("templates")
18
+ @thor_shell = ::Thor::Shell::Basic.new
19
+ @io = io
20
+ @git_ignore = @target_dir.join(".gitignore")
21
+
22
+ FileUtils.touch(@git_ignore)
23
+ FileUtils.touch(@target_dir.join("hatchet.lock"))
24
+ end
25
+
26
+ def call
27
+ write_target(target: ".circleci/config.yml", template: "circleci_template.erb")
28
+ write_target(target: "Gemfile", template: "Gemfile.erb")
29
+ write_target(target: "hatchet.json", template: "hatchet_json.erb")
30
+ write_target(target: "spec/spec_helper.rb", template: "spec_helper.erb")
31
+ write_target(target: "spec/hatchet/buildpack_spec.rb", template: "buildpack_spec.erb")
32
+ write_target(target: ".github/dependabot.yml", template: "dependabot.erb")
33
+ write_target(target: ".github/workflows/check_changelog.yml", template: "check_changelog.erb")
34
+
35
+ add_gitignore(".rspec_status")
36
+ add_gitignore("repos/*")
37
+
38
+ stream("cd #{@target_dir} && bundle install")
39
+ stream("cd #{@target_dir} && hatchet install")
40
+
41
+ @io.puts
42
+ @io.puts "Done, run `bundle exec rspec` to execute your tests"
43
+ @io.puts
44
+ end
45
+
46
+ private def add_gitignore(statement)
47
+ @git_ignore.open("a") {|f| f.puts statement } unless @git_ignore.read.include?(statement)
48
+ end
49
+
50
+ private def stream(command)
51
+ output = ""
52
+ IO.popen(command) do |io|
53
+ until io.eof?
54
+ buffer = io.gets
55
+ output << buffer
56
+ @io.puts(buffer)
57
+ end
58
+ end
59
+ raise "Error running #{command}. Output:\n#{output}" unless $?.success?
60
+ output
61
+ end
62
+
63
+ private def write_target(template: nil, target:, contents: nil)
64
+ if template
65
+ template = @template_dir.join(template)
66
+ contents = ERB.new(template.read).result(binding)
67
+ end
68
+
69
+ target = @target_dir.join(target)
70
+ target.dirname.mkpath # Create directory if it doesn't exist already
71
+
72
+ if target.exist?
73
+ return if contents === target.read # identical
74
+ target.write(contents) if @thor_shell.file_collision(target) { contents }
75
+ else
76
+ target.write(contents)
77
+ end
78
+ end
79
+
80
+ private def cmd(command)
81
+ result = `#{command}`.chomp
82
+ raise "Command #{command} failed:\n#{result}" unless $?.success?
83
+ result
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "parallel_split_test"
4
+ gem "heroku_hatchet"
5
+ gem "rspec-retry"
@@ -0,0 +1,23 @@
1
+ require_relative "../spec_helper.rb"
2
+
3
+ RSpec.describe "This buildpack" do
4
+ it "has its own tests" do
5
+ raise "delete this and replace it with your own logic"
6
+
7
+ # Specify where you want your buildpack to go using :default
8
+ buildpacks = [:default, "heroku/ruby"]
9
+
10
+ # To deploy a different app modify the hatchet.json or
11
+ # commit an app to your source control and use a path
12
+ # instead of "default_ruby" here
13
+ Hatchet::Runner.new("default_ruby", buildpacks: buildpacks).tap do |app|
14
+ app.before_deploy do
15
+ # Modfiy the app here if you need
16
+ end
17
+ app.deploy do
18
+ # Assert the behavior you desire here
19
+ expect(app.output).to match("deployed to Heroku")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ name: Check Changelog
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, reopened, edited, synchronize]
6
+ jobs:
7
+ build:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v1
11
+ - name: Check that CHANGELOG is touched
12
+ run: |
13
+ cat $GITHUB_EVENT_PATH | jq .pull_request.title | grep -i '\[\(\(changelog skip\)\|\(ci skip\)\)\]' || git diff remotes/origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md
@@ -0,0 +1,45 @@
1
+ version: 2
2
+ references:
3
+ unit: &unit
4
+ run:
5
+ name: Run test suite
6
+ command: PARALLEL_SPLIT_TEST_PROCESSES=25 IS_RUNNING_ON_CI=1 bundle exec parallel_split_test spec/
7
+ restore: &restore
8
+ restore_cache:
9
+ keys:
10
+ - v1_bundler_deps-{{ .Environment.CIRCLE_JOB }}
11
+ save: &save
12
+ save_cache:
13
+ paths:
14
+ - ./vendor/bundle
15
+ key: v1_bundler_deps-{{ .Environment.CIRCLE_JOB }} # CIRCLE_JOB e.g. "ruby-2.5"
16
+ hatchet_setup: &hatchet_setup
17
+ run:
18
+ name: Hatchet setup
19
+ command: |
20
+ bundle exec hatchet ci:setup
21
+ bundle: &bundle
22
+ run:
23
+ name: install dependencies
24
+ command: |
25
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
26
+ bundle update
27
+ bundle clean
28
+ jobs:
29
+ "ruby-2.7":
30
+ docker:
31
+ - image: circleci/ruby:2.7
32
+ steps:
33
+ - checkout
34
+ - <<: *restore
35
+ - <<: *bundle
36
+ - <<: *hatchet_setup
37
+ - <<: *unit
38
+ - <<: *save
39
+
40
+ workflows:
41
+ version: 2
42
+ build:
43
+ jobs:
44
+ - "ruby-2.7"
45
+
@@ -0,0 +1,9 @@
1
+ version: 1
2
+ updates:
3
+ - package-ecosystem: "bundler"
4
+ directory: "/"
5
+ open-pull-requests-limit: 1 # Limit concurrent CI runs from executing
6
+ schedule:
7
+ interval: "weekly"
8
+ labels:
9
+ - "dependencies"
@@ -0,0 +1,11 @@
1
+ {
2
+ "ruby_apps": [
3
+ "sharpstone/default_ruby"
4
+ ],
5
+ "node_apps": [
6
+ "heroku/node-js-getting-started"
7
+ ],
8
+ "python_apps": [
9
+ "heroku/python-getting-started"
10
+ ]
11
+ }
@@ -0,0 +1,30 @@
1
+ require "bundler/setup"
2
+
3
+ require 'rspec/retry'
4
+
5
+ ENV["HATCHET_BUILDPACK_BASE"] = "<%= cmd("git config --get remote.origin.url") %>"
6
+
7
+ require 'hatchet'
8
+ require 'pathname'
9
+
10
+ RSpec.configure do |config|
11
+ # Enable flags like --only-failures and --next-failure
12
+ config.example_status_persistence_file_path = ".rspec_status"
13
+ config.verbose_retry = true # show retry status in spec process
14
+ config.default_retry_count = 2 if ENV['IS_RUNNING_ON_CI'] # retry all tests that fail again
15
+
16
+ config.expect_with :rspec do |c|
17
+ c.syntax = :expect
18
+ end
19
+ end
20
+
21
+ def run!(cmd)
22
+ out = `#{cmd}`
23
+ raise "Error running #{cmd}, output: #{out}" unless $?.success?
24
+ out
25
+ end
26
+
27
+ def spec_dir
28
+ Pathname.new(__dir__)
29
+ end
30
+
@@ -1,3 +1,3 @@
1
1
  module Hatchet
2
- VERSION = "7.1.3"
2
+ VERSION = "7.2.0"
3
3
  end
@@ -1,6 +1,28 @@
1
1
  require("spec_helper")
2
2
 
3
3
  describe "AppTest" do
4
+ it "does not modify local files by mistake" do
5
+ Dir.mktmpdir do |dir_1|
6
+ app = Hatchet::Runner.new(dir_1)
7
+ Dir.mktmpdir do |dir_2|
8
+ Dir.chdir(dir_2) do
9
+ FileUtils.touch("foo.txt")
10
+
11
+ app.setup!
12
+ end
13
+
14
+ entries_array = Dir.entries(dir_2)
15
+ entries_array -= ["..", ".", "foo.txt"]
16
+ expect(entries_array).to be_empty
17
+
18
+
19
+ entries_array = Dir.entries(dir_1)
20
+ entries_array -= ["..", ".", "foo.txt"]
21
+ expect(entries_array).to be_empty
22
+ end
23
+ end
24
+ end
25
+
4
26
  it "rate throttles `git push` " do
5
27
  app = Hatchet::GitApp.new("default_ruby")
6
28
  def app.git_push_heroku_yall
@@ -13,14 +13,15 @@ describe "LocalRepoTest" do
13
13
 
14
14
  it "repos checked into git" do
15
15
  begin
16
- app = Hatchet::App.new("repo_fixtures/different-folder-for-checked-in-repos/default_ruby")
17
- app.in_directory do
18
- expect(Dir.exist?(".git")).to eq(false)
19
- app.setup!
20
- expect(Dir.exist?(".git")).to eq(true)
16
+ fixture_dir = "repo_fixtures/different-folder-for-checked-in-repos/default_ruby"
17
+ app = Hatchet::App.new(fixture_dir)
18
+ def app.push_with_retry!; end
19
+
20
+ expect(Dir.exist?("#{fixture_dir}/.git")).to be_falsey
21
+
22
+ app.deploy do
23
+ expect(Dir.exist?(".git")).to be_truthy
21
24
  end
22
- ensure
23
- app.teardown! if app
24
25
  end
25
26
  end
26
27
  end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe "Hatchet::Init" do
4
+ def fake_buildpack_dir
5
+ Dir.mktmpdir do |dir|
6
+ FileUtils.mkdir_p("#{dir}/bin")
7
+ yield dir
8
+ end
9
+ end
10
+ it "raises an error when not pointing at the right directory" do
11
+ Dir.mktmpdir do |dir|
12
+ expect {
13
+ Hatchet::InitProject.new(dir: dir)
14
+ }.to raise_error(/Must run in a directory with a buildpack/)
15
+ end
16
+ end
17
+
18
+ # write_target(target: ".circleci/config.yml", template: "circleci_template.erb")
19
+ # write_target(target: "Gemfile", template: "Gemfile.erb")
20
+ # write_target(target: "hatchet.json", contents: "{}")
21
+ # write_target(target: "hatchet.lock", contents: YAML.dump({}))
22
+ # write_target(target: "spec/spec_helper.rb", template: "spec_helper.erb")
23
+ # write_target(target: "spec/hatchet/buildpack_spec.rb", template: "buildpack_spec.erb")
24
+ # write_target(target: ".github/dependabot.yml", template: "dependabot.erb")
25
+
26
+ it "generates files" do
27
+ fake_buildpack_dir do |dir|
28
+ fake_stdout = StringIO.new
29
+ init = Hatchet::InitProject.new(dir: dir, io: fake_stdout)
30
+ init.call
31
+
32
+ circle_ci_file = Pathname.new(dir).join(".circleci/config.yml")
33
+ expect(circle_ci_file.read).to match("parallel_split_test")
34
+
35
+ %W{
36
+ .circleci/config.yml
37
+ Gemfile
38
+ hatchet.json
39
+ hatchet.lock
40
+ spec/spec_helper.rb
41
+ spec/hatchet/buildpack_spec.rb
42
+ .github/dependabot.yml
43
+ .github/workflows/check_changelog.yml
44
+ .gitignore
45
+ }.each do |path|
46
+ expect(Pathname.new(dir).join(path)).to exist
47
+ end
48
+
49
+ expect(fake_stdout.string).to match("Bundle complete")
50
+ end
51
+ end
52
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku_hatchet
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.3
4
+ version: 7.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Schneeman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-09 00:00:00.000000000 Z
11
+ date: 2020-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: platform-api
@@ -192,11 +192,19 @@ files:
192
192
  - lib/hatchet/app.rb
193
193
  - lib/hatchet/config.rb
194
194
  - lib/hatchet/git_app.rb
195
+ - lib/hatchet/init_project.rb
195
196
  - lib/hatchet/reaper.rb
196
197
  - lib/hatchet/reaper/app_age.rb
197
198
  - lib/hatchet/reaper/reaper_throttle.rb
198
199
  - lib/hatchet/shell_throttle.rb
199
200
  - lib/hatchet/tasks.rb
201
+ - lib/hatchet/templates/Gemfile.erb
202
+ - lib/hatchet/templates/buildpack_spec.erb
203
+ - lib/hatchet/templates/check_changelog.erb
204
+ - lib/hatchet/templates/circleci_template.erb
205
+ - lib/hatchet/templates/dependabot.erb
206
+ - lib/hatchet/templates/hatchet_json.erb
207
+ - lib/hatchet/templates/spec_helper.erb
200
208
  - lib/hatchet/test_run.rb
201
209
  - lib/hatchet/version.rb
202
210
  - repo_fixtures/different-folder-for-checked-in-repos/default_ruby/Gemfile
@@ -211,6 +219,7 @@ files:
211
219
  - spec/hatchet/local_repo_spec.rb
212
220
  - spec/hatchet/lock_spec.rb
213
221
  - spec/spec_helper.rb
222
+ - spec/unit/init_spec.rb
214
223
  - spec/unit/reaper_spec.rb
215
224
  - spec/unit/shell_throttle.rb
216
225
  - tmp/parallel_runtime_test.log
@@ -248,5 +257,6 @@ test_files:
248
257
  - spec/hatchet/local_repo_spec.rb
249
258
  - spec/hatchet/lock_spec.rb
250
259
  - spec/spec_helper.rb
260
+ - spec/unit/init_spec.rb
251
261
  - spec/unit/reaper_spec.rb
252
262
  - spec/unit/shell_throttle.rb