hanami-devtools 2023.02.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rubocop.yml +158 -0
  4. data/Gemfile +4 -0
  5. data/README.md +35 -0
  6. data/Rakefile +4 -0
  7. data/bin/console +15 -0
  8. data/bin/setup +8 -0
  9. data/hanami-devtools.gemspec +42 -0
  10. data/lib/hanami/devtools/integration/bundler.rb +174 -0
  11. data/lib/hanami/devtools/integration/capybara.rb +20 -0
  12. data/lib/hanami/devtools/integration/cli.rb +57 -0
  13. data/lib/hanami/devtools/integration/coverage.rb +55 -0
  14. data/lib/hanami/devtools/integration/dns.rb +26 -0
  15. data/lib/hanami/devtools/integration/env.rb +97 -0
  16. data/lib/hanami/devtools/integration/files.rb +108 -0
  17. data/lib/hanami/devtools/integration/gemfile.rb +38 -0
  18. data/lib/hanami/devtools/integration/hanami_commands.rb +169 -0
  19. data/lib/hanami/devtools/integration/platform/engine.rb +32 -0
  20. data/lib/hanami/devtools/integration/platform/matcher.rb +79 -0
  21. data/lib/hanami/devtools/integration/platform/os.rb +21 -0
  22. data/lib/hanami/devtools/integration/platform.rb +22 -0
  23. data/lib/hanami/devtools/integration/project_without_hanami_model.rb +26 -0
  24. data/lib/hanami/devtools/integration/rack_test.rb +87 -0
  25. data/lib/hanami/devtools/integration/random_port.rb +46 -0
  26. data/lib/hanami/devtools/integration/retry.rb +36 -0
  27. data/lib/hanami/devtools/integration/silently.rb +35 -0
  28. data/lib/hanami/devtools/integration/with_clean_env_project.rb +29 -0
  29. data/lib/hanami/devtools/integration/with_directory.rb +30 -0
  30. data/lib/hanami/devtools/integration/with_project.rb +77 -0
  31. data/lib/hanami/devtools/integration/with_system_tmp_directory.rb +24 -0
  32. data/lib/hanami/devtools/integration/with_tmp_directory.rb +48 -0
  33. data/lib/hanami/devtools/integration/within_project_directory.rb +37 -0
  34. data/lib/hanami/devtools/integration.rb +4 -0
  35. data/lib/hanami/devtools/rake_helper.rb +31 -0
  36. data/lib/hanami/devtools/rake_tasks.rb +4 -0
  37. data/lib/hanami/devtools/unit/support/coverage.rb +10 -0
  38. data/lib/hanami/devtools/unit/support/silence_deprecations.rb +12 -0
  39. data/lib/hanami/devtools/unit.rb +4 -0
  40. data/lib/hanami/devtools/version.rb +7 -0
  41. data/lib/hanami/devtools.rb +8 -0
  42. data/script/setup +36 -0
  43. metadata +266 -0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "timeout"
5
+
6
+ module RSpec
7
+ module Support
8
+ # Returns a random, non-sudo, free port
9
+ #
10
+ # @since 0.2.0
11
+ module RandomPort
12
+ HOST = "localhost"
13
+ PORT_RANGE = 1024..65_535
14
+ TIMEOUT = 1 # second
15
+
16
+ def self.call
17
+ result = -1
18
+
19
+ loop do
20
+ result = Kernel.rand(PORT_RANGE)
21
+ break unless open?(result)
22
+ end
23
+
24
+ result
25
+ end
26
+
27
+ def self.open?(port) # rubocop:disable Metrics/MethodLength
28
+ Timeout.timeout(TIMEOUT) do
29
+ begin
30
+ s = TCPSocket.new(HOST, port)
31
+ s.close
32
+ return true
33
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EADDRNOTAVAIL
34
+ return false
35
+ end
36
+ end
37
+
38
+ false
39
+ rescue Timeout::Error
40
+ false
41
+ end
42
+
43
+ private_class_method :open?
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Support
5
+ # Implement retry logic.
6
+ #
7
+ # It may happen that the setup of a test is slower then expected.
8
+ # Instead of let it to blow up, we can retry after a few seconds.
9
+ #
10
+ # @since 0.2.0
11
+ module Retry
12
+ private
13
+
14
+ def retry_exec(exception) # rubocop:disable Metrics/MethodLength
15
+ attempts = 1
16
+ max_retry_attempts = Platform.match do
17
+ engine(:ruby) { 10 }
18
+ engine(:jruby) { 20 }
19
+ end
20
+
21
+ begin
22
+ sleep 1
23
+ yield
24
+ rescue exception
25
+ raise if attempts > max_retry_attempts
26
+ attempts += 1
27
+ retry
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ RSpec.configure do |config|
35
+ config.include RSpec::Support::Retry, type: :integration
36
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+
5
+ module RSpec
6
+ # RSpec support utilities
7
+ #
8
+ # @since 0.2.0
9
+ module Support
10
+ def self.silently(cmd)
11
+ out = Tempfile.new("hanami-out")
12
+ result = system(cmd, out: out.path)
13
+
14
+ return if result
15
+
16
+ out.rewind
17
+ fail "#{cmd} failed:\n#{out.read}" # rubocop:disable Style/SignalException
18
+ end
19
+
20
+ # Don't print unnecessary output during tests
21
+ #
22
+ # @since 0.2.0
23
+ module Silently
24
+ private
25
+
26
+ def silently(cmd)
27
+ Support.silently(cmd)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ RSpec.configure do |config|
34
+ config.include RSpec::Support::Silently, type: :integration
35
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/devtools/integration/with_project"
4
+ require "hanami/devtools/integration/bundler"
5
+
6
+ module RSpec
7
+ module Support
8
+ # Generate a Hanami project with a Bundler clean environment.
9
+ #
10
+ # @since 0.2.0
11
+ #
12
+ # @see http://bundler.io/man/bundle-exec.1.html#Shelling-out
13
+ module WithCleanEnvProject
14
+ private
15
+
16
+ def with_clean_env_project(project = "bookshelf", args = {})
17
+ RSpec::Support::Bundler.with_clean_env do
18
+ with_project(project, args) do
19
+ yield
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ RSpec.configure do |config|
28
+ config.include RSpec::Support::WithCleanEnvProject, type: :integration
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module RSpec
6
+ module Support
7
+ # Execute the given code inside a specified directory.
8
+ #
9
+ # NOTE: this changes the current `Dir.pwd`
10
+ #
11
+ # @since 0.2.0
12
+ module WithDirectory
13
+ private
14
+
15
+ def with_directory(directory)
16
+ current = Dir.pwd
17
+ target = Pathname.new(Dir.pwd).join(directory)
18
+
19
+ Dir.chdir(target)
20
+ yield
21
+ ensure
22
+ Dir.chdir(current)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ RSpec.configure do |config|
29
+ config.include RSpec::Support::WithDirectory
30
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/devtools/integration/silently"
4
+ require "hanami/devtools/integration/bundler"
5
+ require "hanami/devtools/integration/with_tmp_directory"
6
+ require "hanami/devtools/integration/within_project_directory"
7
+
8
+ module RSpec
9
+ module Support
10
+ # Generates a Hanami project
11
+ #
12
+ # @since 0.2.0
13
+ module WithProject
14
+ private
15
+
16
+ KNOWN_ARGUMENTS = %i[database template test].freeze
17
+
18
+ def with_project(project = "bookshelf", args = {}) # rubocop:disable Metrics/MethodLength
19
+ with_tmp_directory do
20
+ create_project(project, args)
21
+
22
+ within_project_directory(project) do
23
+ begin
24
+ setup_gemfile(gems: gem_dependencies(args), exclude_gems: args.fetch(:exclude_gems, []))
25
+ bundle_install
26
+ yield
27
+ ensure
28
+ restore_gemfile
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_project(project, args)
35
+ silently "hanami new #{project}#{_create_project_args(args)}"
36
+ end
37
+
38
+ def gem_dependencies(args) # rubocop:disable Metrics/MethodLength
39
+ result = args.fetch(:gems, [])
40
+
41
+ case args.fetch(:console, nil)
42
+ when :pry
43
+ result << "pry"
44
+ when :ripl
45
+ result << "ripl"
46
+ end
47
+
48
+ case args.fetch(:server, nil)
49
+ when :puma
50
+ result << "puma"
51
+ when :unicorn
52
+ result << "unicorn"
53
+ when :thin
54
+ result << "thin"
55
+ end
56
+
57
+ result
58
+ end
59
+
60
+ def _create_project_args(args)
61
+ return if args.empty?
62
+
63
+ flags = args.dup.keep_if { |k, _| KNOWN_ARGUMENTS.include?(k) }
64
+
65
+ result = flags.map do |arg, value|
66
+ "--#{arg}=#{value}"
67
+ end.join(" ")
68
+
69
+ " #{result}"
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ RSpec.configure do |config|
76
+ config.include RSpec::Support::WithProject, type: :integration
77
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/devtools/integration/with_tmp_directory"
4
+
5
+ module RSpec
6
+ module Support
7
+ # Execute the given code inside the system temporary directory (aka `/tmp`).
8
+ #
9
+ # NOTE: this changes the current `Dir.pwd`
10
+ #
11
+ # @since 0.2.0
12
+ module WithSystemTmpDirectory
13
+ private
14
+
15
+ def with_system_tmp_directory(&blk)
16
+ with_tmp_directory(Dir.mktmpdir, &blk)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ RSpec.configure do |config|
23
+ config.include RSpec::Support::WithSystemTmpDirectory, type: :integration
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "hanami/utils/files"
5
+ require "hanami/devtools/integration/with_directory"
6
+
7
+ module RSpec
8
+ module Support
9
+ # Execute the given code inside a temporary directory.
10
+ #
11
+ # NOTE: this changes the current `Dir.pwd`
12
+ #
13
+ # @since 0.2.0
14
+ module WithTmpDirectory
15
+ private
16
+
17
+ def with_tmp_directory(dir = Pathname.new("tmp").join("aruba"))
18
+ delete_tmp_directory(dir)
19
+ create_tmp_directory(dir)
20
+
21
+ with_directory(dir) do
22
+ yield
23
+ end
24
+ ensure
25
+ delete_tmp_directory(dir)
26
+ end
27
+
28
+ def create_tmp_directory(dir)
29
+ FileUtils.mkdir_p(dir)
30
+ end
31
+
32
+ def delete_tmp_directory(dir)
33
+ directory = case dir
34
+ when Pathname
35
+ dir
36
+ when String
37
+ Pathname.new(dir)
38
+ end
39
+
40
+ Hanami::Utils::Files.delete_directory(directory) if directory.exist?
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ RSpec.configure do |config|
47
+ config.include RSpec::Support::WithTmpDirectory, type: :integration
48
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "hanami/devtools/integration/with_directory"
5
+ require "hanami/devtools/integration/env"
6
+
7
+ module RSpec
8
+ module Support
9
+ # Execute the given code inside the Hanami project directory (at the root).
10
+ #
11
+ # NOTE: this changes the current `Dir.pwd`
12
+ #
13
+ # @since 0.2.0
14
+ module WithinProjectDirectory
15
+ private
16
+
17
+ def within_project_directory(project)
18
+ cd(project.to_s) do
19
+ # Aruba resets ENV and its API to set new env vars is broken.
20
+ #
21
+ # We need to manually setup the following env vars:
22
+ #
23
+ # ENV["PATH"] is required by Capybara's selenium/poltergeist drivers
24
+ ENV["PATH"] = RSpec::Support::Env.fetch_from_original("PATH")
25
+ # Bundler on CI can't find HOME and it fails to run Hanami commands
26
+ ENV["HOME"] = RSpec::Support::Env.fetch_from_original("HOME")
27
+
28
+ yield
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ RSpec.configure do |config|
36
+ config.include RSpec::Support::WithinProjectDirectory
37
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils"
4
+ Hanami::Utils.require!("#{__dir__}/integration")
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+
5
+ module Hanami
6
+ module Devtools
7
+ # Distribute Rake tasks
8
+ class RakeHelper
9
+ include Rake::DSL
10
+
11
+ def self.install_tasks
12
+ new.install
13
+ end
14
+
15
+ def install # rubocop:disable Metrics/MethodLength
16
+ namespace :codecov do
17
+ desc "Uploads the latest simplecov result set to codecov.io"
18
+ task :upload do
19
+ if ENV["CI"]
20
+ require "simplecov"
21
+ require "codecov"
22
+
23
+ formatter = SimpleCov::Formatter::Codecov.new
24
+ formatter.format(SimpleCov::ResultMerger.merged_result)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/devtools/rake_helper"
4
+ Hanami::Devtools::RakeHelper.install_tasks
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ if ENV["CI"]
4
+ require "simplecov"
5
+
6
+ SimpleCov.command_name ENV["SIMPLECOV_COMMAND_NAME"] || "spec:unit"
7
+ SimpleCov.start do
8
+ add_filter(/spec/)
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "hanami/utils/io"
5
+
6
+ RSpec.configure do |config|
7
+ config.around(:example, silence_deprecations: true) do |example|
8
+ Hanami::Utils::IO.silence_warnings do
9
+ example.run
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils"
4
+ Hanami::Utils.require!("#{__dir__}/unit")
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Devtools
5
+ VERSION = "2023.02.16"
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ # Hanami devtools
5
+ module Devtools
6
+ require "hanami/devtools/version"
7
+ end
8
+ end
data/script/setup ADDED
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ banner() {
6
+ echo "Using hanami-devtools at: $(git rev-parse --short HEAD)"
7
+ }
8
+
9
+ install_hanami() {
10
+ hanami_gems=($(bundle show | grep hanami))
11
+
12
+ for bundler_gem_info in "${hanami_gems[@]}"
13
+ do
14
+ local gem_name=$(echo $bundler_gem_info | cut -d' ' -f 4)
15
+ local gem_directory=$(bundle show $gem_name)
16
+
17
+
18
+ pushd $gem_directory > /dev/null
19
+ find . -name \*.gem -delete
20
+ gem build $gem_name.gemspec > /dev/null
21
+
22
+ local pkg=($gem_name-*.gem)
23
+ gem install --force $pkg > /dev/null
24
+ rm $pkg
25
+
26
+ echo Installed $pkg from $gem_directory
27
+ popd > /dev/null
28
+ done
29
+ }
30
+
31
+ main() {
32
+ banner &&
33
+ install_hanami
34
+ }
35
+
36
+ main