neospec 0.0.1

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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.buildkite/bin/check-formatting +7 -0
  3. data/.buildkite/bin/install-asdf +16 -0
  4. data/.buildkite/bin/install-ruby +11 -0
  5. data/.buildkite/bin/install-taylor +15 -0
  6. data/.buildkite/bin/setup-ruby +5 -0
  7. data/.buildkite/bin/test-ruby +8 -0
  8. data/.buildkite/bin/test-taylor +8 -0
  9. data/.buildkite/pipeline.yml +52 -0
  10. data/.tool-versions +1 -0
  11. data/Gemfile +4 -0
  12. data/Gemfile.lock +66 -0
  13. data/README.md +43 -0
  14. data/Rakefile +16 -0
  15. data/docs/example_spec.rb +28 -0
  16. data/lib/neospec/color.rb +8 -0
  17. data/lib/neospec/expector.rb +55 -0
  18. data/lib/neospec/logger/basic.rb +32 -0
  19. data/lib/neospec/logger/symbols.rb +18 -0
  20. data/lib/neospec/report/basic.rb +57 -0
  21. data/lib/neospec/results.rb +29 -0
  22. data/lib/neospec/runner/basic.rb +11 -0
  23. data/lib/neospec/spec/result/failure.rb +14 -0
  24. data/lib/neospec/spec/result.rb +31 -0
  25. data/lib/neospec/spec.rb +66 -0
  26. data/lib/neospec/suite.rb +28 -0
  27. data/lib/neospec/version.rb +3 -0
  28. data/lib/neospec.rb +38 -0
  29. data/neospec.gemspec +30 -0
  30. data/spec/neospec/expector_spec.rb +201 -0
  31. data/spec/neospec/logger/basic_spec.rb +101 -0
  32. data/spec/neospec/logger/symbols_spec.rb +54 -0
  33. data/spec/neospec/report/basic_spec.rb +115 -0
  34. data/spec/neospec/results_spec.rb +151 -0
  35. data/spec/neospec/runner/basic_spec.rb +27 -0
  36. data/spec/neospec/spec/result_spec.rb +64 -0
  37. data/spec/neospec/spec_spec.rb +103 -0
  38. data/spec/neospec/suite_spec.rb +56 -0
  39. data/spec/neospec/version_spec.rb +3 -0
  40. data/spec/neospec_spec.rb +11 -0
  41. data/spec/run.rb +30 -0
  42. data/spec/support/test_logger.rb +15 -0
  43. data/spec/support/test_outputter.rb +15 -0
  44. metadata +97 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 38bd481896e0d5370273be416eff657d82318f3e1ff9f517bdb16bb14e80167e
4
+ data.tar.gz: 5583a0d624da8f0b6a1f9afc0590ec565f5cbe5aa1ec2bdca5eb1d69a7235cf4
5
+ SHA512:
6
+ metadata.gz: 5bb3bffd7c3b17f9ca90109e6ec9097de59d353b2ae7c6ee4747c7f8ab55dd580f7059e15c4c205c46bf99450ea36d2a11e6281560ae4dd60f4f79f214365984
7
+ data.tar.gz: 803bb7a2562b2138d40e04857c0b2463a8bfdb99084e5ee06479e5dff2322ddfe18168364ab932fa3fc9e8a2dc4ded3da643bea0c19d51ccff6b156f9d306274
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ .buildkite/bin/install-ruby
5
+ .buildkite/bin/setup-ruby
6
+
7
+ bundle exec rake standard
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ ! -f "/root/.asdf/bin/asdf" ]; then
5
+ ASDF_VERSION=v0.16.4
6
+ ASDF_PLATFORM=amd64
7
+ ASDF_URL=https://github.com/asdf-vm/asdf/releases/download/$ASDF_VERSION/asdf-$ASDF_VERSION-linux-$ASDF_PLATFORM.tar.gz
8
+
9
+ wget -O /root/asdf.tar.gz "${ASDF_URL}"
10
+ tar xfv /root/asdf.tar.gz
11
+ mkdir -p /root/.asdf/bin/
12
+ mv asdf /root/.asdf/bin/asdf
13
+ rm /root/asdf.tar.gz
14
+
15
+ asdf plugin add ruby
16
+ fi
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ .buildkite/bin/install-asdf
5
+
6
+ if [[ -n ${1+x} ]]; then
7
+ asdf set ruby $1
8
+ fi
9
+
10
+ asdf plugin update ruby
11
+ asdf install ruby
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ TAYLOR_VERSION=$1
5
+
6
+ if [ ! -f "/root/.taylor-versions/taylor-${TAYLOR_VERSION}" ]; then
7
+ TAYLOR_URL=https://github.com/HellRok/Taylor/releases/download/${TAYLOR_VERSION}/taylor-linux-${TAYLOR_VERSION}.zip
8
+
9
+ wget -O /root/taylor.zip "${TAYLOR_URL}"
10
+ unzip /root/taylor.zip
11
+ mkdir -p /root/.taylor-versions/
12
+ mv taylor /root/.taylor-versions/taylor-${TAYLOR_VERSION}
13
+ rm /root/taylor.zip
14
+ fi
15
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euxo pipefail
3
+
4
+ gem install bundler
5
+ bundle install
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ RUBY_VERSION=$1
5
+
6
+ .buildkite/bin/install-ruby $RUBY_VERSION
7
+
8
+ ruby spec/run.rb
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ TAYLOR_VERSION=$1
5
+
6
+ .buildkite/bin/install-taylor $TAYLOR_VERSION
7
+
8
+ /root/.taylor-versions/taylor-${TAYLOR_VERSION} spec/run.rb
@@ -0,0 +1,52 @@
1
+ steps:
2
+ - label: 'Formatting'
3
+ commands:
4
+ - .buildkite/bin/check-formatting
5
+
6
+ - label: 'Ruby - 3.5'
7
+ commands:
8
+ - .buildkite/bin/test-ruby 3.5.0-preview1
9
+
10
+ - label: 'Ruby - 3.4'
11
+ commands:
12
+ - .buildkite/bin/test-ruby 3.4.5
13
+
14
+ - label: 'Ruby - 3.3'
15
+ commands:
16
+ - .buildkite/bin/test-ruby 3.3.8
17
+
18
+ - label: 'Ruby - 3.2'
19
+ commands:
20
+ - .buildkite/bin/test-ruby 3.2.8
21
+
22
+ - label: 'Ruby - 3.1'
23
+ commands:
24
+ - .buildkite/bin/test-ruby 3.1.7
25
+
26
+ - label: 'Ruby - 3.0'
27
+ commands:
28
+ - .buildkite/bin/test-ruby 3.0.7
29
+
30
+ - label: 'Ruby - 2.7'
31
+ commands:
32
+ - .buildkite/bin/test-ruby 2.7.8
33
+
34
+ - label: 'Ruby - 2.6'
35
+ commands:
36
+ - .buildkite/bin/test-ruby 2.6.10
37
+
38
+ - label: 'JRuby - 10'
39
+ commands:
40
+ - .buildkite/bin/test-ruby jruby-10.0.0.1
41
+
42
+ - label: 'JRuby - 9'
43
+ commands:
44
+ - .buildkite/bin/test-ruby jruby-9.4.13.0
45
+
46
+ - label: 'TruffleRuby - 24'
47
+ commands:
48
+ - .buildkite/bin/test-ruby truffleruby-24.2.1
49
+
50
+ - label: 'Taylor - v0.3.14.1'
51
+ commands:
52
+ - .buildkite/bin/test-taylor v0.3.14.1
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.4.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org" do
2
+ gem "rake"
3
+ gem "standardrb"
4
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,66 @@
1
+ GEM
2
+ specs:
3
+
4
+ GEM
5
+ remote: https://rubygems.org/
6
+ specs:
7
+ ast (2.4.3)
8
+ json (2.13.1)
9
+ language_server-protocol (3.17.0.5)
10
+ lint_roller (1.1.0)
11
+ parallel (1.27.0)
12
+ parser (3.3.8.0)
13
+ ast (~> 2.4.1)
14
+ racc
15
+ prism (1.4.0)
16
+ racc (1.8.1)
17
+ rainbow (3.1.1)
18
+ rake (13.3.0)
19
+ regexp_parser (2.10.0)
20
+ rubocop (1.75.8)
21
+ json (~> 2.3)
22
+ language_server-protocol (~> 3.17.0.2)
23
+ lint_roller (~> 1.1.0)
24
+ parallel (~> 1.10)
25
+ parser (>= 3.3.0.2)
26
+ rainbow (>= 2.2.2, < 4.0)
27
+ regexp_parser (>= 2.9.3, < 3.0)
28
+ rubocop-ast (>= 1.44.0, < 2.0)
29
+ ruby-progressbar (~> 1.7)
30
+ unicode-display_width (>= 2.4.0, < 4.0)
31
+ rubocop-ast (1.46.0)
32
+ parser (>= 3.3.7.2)
33
+ prism (~> 1.4)
34
+ rubocop-performance (1.25.0)
35
+ lint_roller (~> 1.1)
36
+ rubocop (>= 1.75.0, < 2.0)
37
+ rubocop-ast (>= 1.38.0, < 2.0)
38
+ ruby-progressbar (1.13.0)
39
+ standard (1.50.0)
40
+ language_server-protocol (~> 3.17.0.2)
41
+ lint_roller (~> 1.0)
42
+ rubocop (~> 1.75.5)
43
+ standard-custom (~> 1.0.0)
44
+ standard-performance (~> 1.8)
45
+ standard-custom (1.0.2)
46
+ lint_roller (~> 1.0)
47
+ rubocop (~> 1.50)
48
+ standard-performance (1.8.0)
49
+ lint_roller (~> 1.1)
50
+ rubocop-performance (~> 1.25.0)
51
+ standardrb (1.0.1)
52
+ standard
53
+ unicode-display_width (3.1.4)
54
+ unicode-emoji (~> 4.0, >= 4.0.4)
55
+ unicode-emoji (4.0.4)
56
+
57
+ PLATFORMS
58
+ arm64-darwin-24
59
+ ruby
60
+
61
+ DEPENDENCIES
62
+ rake!
63
+ standardrb!
64
+
65
+ BUNDLED WITH
66
+ 2.6.9
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # neospec
2
+
3
+ ## Usage
4
+
5
+ Add `gem "neospec"` to your `Gemfile` and run `bundle install`.
6
+
7
+ Then it can be as simple as:
8
+
9
+ ```ruby
10
+ require "neospec"
11
+
12
+ unit = Neospec::Suite.new
13
+ neospec = Neospec.new(suites: [unit])
14
+
15
+ unit.describe "An example test" do
16
+ Given "apples are ripe" do
17
+ @apples = :ripe
18
+ end
19
+
20
+ And "bananas are ripe" do
21
+ @bananas = :ripe
22
+ end
23
+
24
+ Then "apples and bananas are as ripe as each other" do
25
+ expect(@apples).to_equal(@bananas)
26
+ end
27
+
28
+ But "the bananas become TOO ripe" do
29
+ @bananas = :over_ripe
30
+ end
31
+
32
+ Then "apples and bananas are now not as ripe as each other" do
33
+ expect(@apples).not_to_equal(@bananas)
34
+ end
35
+ end
36
+
37
+ neospec.run!
38
+ ```
39
+
40
+ ## Not yet implemented
41
+
42
+ - Early exit upon hitting a failure
43
+ - Expect errors
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "standard/rake"
2
+
3
+ task neospec: ["neospec:ruby", "neospec:taylor"]
4
+
5
+ task "neospec:ruby" do
6
+ require "./spec/run"
7
+ end
8
+
9
+ task "neospec:taylor" do
10
+ exec "taylor spec/run.rb"
11
+ end
12
+
13
+ task "example" do
14
+ $LOAD_PATH << "./lib"
15
+ require "./docs/example_spec"
16
+ end
@@ -0,0 +1,28 @@
1
+ require "neospec"
2
+
3
+ unit = Neospec::Suite.new
4
+ neospec = Neospec.new(suites: [unit])
5
+
6
+ unit.describe "An example test" do
7
+ Given "apples are ripe" do
8
+ @apples = :ripe
9
+ end
10
+
11
+ And "bananas are ripe" do
12
+ @bananas = :ripe
13
+ end
14
+
15
+ Then "apples and bananas are as ripe as each other" do
16
+ expect(@apples).to_equal(@bananas)
17
+ end
18
+
19
+ But "the bananas become TOO ripe" do
20
+ @bananas = :over_ripe
21
+ end
22
+
23
+ Then "apples and bananas are now not as ripe as each other" do
24
+ expect(@apples).not_to_equal(@bananas)
25
+ end
26
+ end
27
+
28
+ neospec.run!
@@ -0,0 +1,8 @@
1
+ class Neospec
2
+ module Color
3
+ GREEN = "\033[0;32m"
4
+ RED = "\033[0;31m"
5
+ BLUE = "\033[0;34m"
6
+ RESET = "\033[0m"
7
+ end
8
+ end
@@ -0,0 +1,55 @@
1
+ class Neospec
2
+ class Expector
3
+ attr_reader :failure, :result
4
+
5
+ def initialize(result:, actual:, stack:, logger:)
6
+ @actual = actual
7
+ @stack = stack
8
+ @logger = logger
9
+ @result = result
10
+ end
11
+
12
+ def log(message, context: nil)
13
+ @logger.log(message, context: context, result: @result)
14
+ end
15
+
16
+ def succeeded(message)
17
+ @result.expectations += 1
18
+ log(message, context: :expect)
19
+ end
20
+
21
+ def failed(message)
22
+ @result.expectations += 1
23
+ @result.failures << Neospec::Spec::Result::Failure.new(
24
+ stack: @stack,
25
+ message: "Expected #{message}"
26
+ )
27
+
28
+ log(message, context: :expect)
29
+ end
30
+
31
+ def to_equal(expected)
32
+ if @actual == expected
33
+ succeeded "to be equal"
34
+ else
35
+ failed "'#{expected}' to equal '#{@actual}'"
36
+ end
37
+ end
38
+
39
+ def not_to_equal(expected)
40
+ if @actual != expected
41
+ succeeded "not to be equal"
42
+ else
43
+ failed "'#{expected}' not to equal '#{@actual}'"
44
+ end
45
+ end
46
+
47
+ def to_be_a(expected)
48
+ if @actual.is_a?(expected)
49
+ succeeded "to be a #{expected}"
50
+ else
51
+ failed "'#{expected}' to equal '#{@actual.class}'"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
1
+ class Neospec
2
+ module Logger
3
+ class Basic
4
+ def initialize(color: true, output: $stdout)
5
+ @color = color
6
+ @output = output
7
+ end
8
+
9
+ def log(message, context: nil, result: nil)
10
+ case context
11
+ when :describe
12
+ if @color
13
+ @output.puts "#{Neospec::Color::BLUE}#{message}#{Neospec::Color::RESET}"
14
+ else
15
+ @output.puts message
16
+ end
17
+ when :expect
18
+ if @color
19
+ str = " "
20
+ str << (result.successful? ? "#{Neospec::Color::GREEN}✓" : "#{Neospec::Color::RED}✗")
21
+ str << " #{context} #{message}#{Neospec::Color::RESET}"
22
+ @output.puts str
23
+ else
24
+ @output.puts " #{result.successful? ? "✓" : "✗"} #{context} #{message}"
25
+ end
26
+ else
27
+ @output.puts " #{context} #{message}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ class Neospec
2
+ module Logger
3
+ class Symbols
4
+ def initialize(color: true, output: $stdout)
5
+ @color = color
6
+ @output = output
7
+ end
8
+
9
+ def log(message, context: nil, result: nil)
10
+ if context == :expect
11
+ @output.write "#{
12
+ result.successful? ? "#{Neospec::Color::GREEN}✓" : "#{Neospec::Color::RED}✗"
13
+ }#{Neospec::Color::RESET}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ class Neospec
2
+ module Report
3
+ module Basic
4
+ def self.call(results, output: $stdout)
5
+ output.puts "\n#{successes(results)}#{failures(results)}"
6
+ end
7
+
8
+ def self.successes(results)
9
+ <<~STR.chomp
10
+ Finished in #{formatted_duration(results.duration)}
11
+
12
+ Results:
13
+ Specs:\t#{results.specs.size}
14
+ Expectations:\t#{results.expectations}
15
+ STR
16
+ end
17
+
18
+ def self.failures(results)
19
+ return if results.successful?
20
+
21
+ output = "\n Failures:\t#{results.failures.size}\n\n"
22
+ output << "Failures:\n"
23
+
24
+ output += results.failures.map do |failure|
25
+ failure_output = [" #{Neospec::Color::RED}#{failure.message}#{Neospec::Color::RESET}"]
26
+ failure.stack.first(5).each do |location|
27
+ failure_output << " > #{location}"
28
+ end
29
+
30
+ failure_output.join("\n")
31
+ end.join("\n\n")
32
+
33
+ output
34
+ end
35
+
36
+ def self.formatted_duration(duration)
37
+ if duration < 1
38
+ "#{(duration * 1000).round(2)} milliseconds"
39
+ elsif duration < 60
40
+ "#{duration.round(2)} seconds"
41
+ elsif duration < 3600
42
+ minutes = (duration / 60).to_i
43
+ seconds = (duration % 60).to_i
44
+ "#{minutes} minute#{(minutes == 1) ? "" : "s"} #{seconds} second#{(seconds == 1) ? "" : "s"}"
45
+ elsif duration < 86400
46
+ hours = (duration / 3600).to_i
47
+ minutes = ((duration % 3600) / 60).to_i
48
+ "#{hours} hour#{(hours == 1) ? "" : "s"} #{minutes} minute#{(minutes == 1) ? "" : "s"}"
49
+ else
50
+ days = (duration / 86400).to_i
51
+ hours = ((duration % 86400) / 3600).to_i
52
+ "#{days} day#{(days == 1) ? "" : "s"} #{hours} hour#{(hours == 1) ? "" : "s"}"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ class Neospec
2
+ class Results
3
+ attr_accessor :suites
4
+
5
+ def initialize(suites: [])
6
+ @suites = suites
7
+ end
8
+
9
+ def specs
10
+ @suites.flat_map(&:specs)
11
+ end
12
+
13
+ def successful?
14
+ specs.all?(&:successful?)
15
+ end
16
+
17
+ def failures
18
+ specs.flat_map(&:failures)
19
+ end
20
+
21
+ def duration
22
+ specs.sum(0, &:duration)
23
+ end
24
+
25
+ def expectations
26
+ specs.sum(0, &:expectations)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ class Neospec
2
+ module Runner
3
+ class Basic
4
+ def run(logger:, suite:)
5
+ suite.specs.each do |spec|
6
+ spec.run(logger: logger)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ class Neospec
2
+ class Spec
3
+ class Result
4
+ class Failure
5
+ attr_reader :stack, :message
6
+
7
+ def initialize(stack:, message:)
8
+ @stack = stack
9
+ @message = message
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ class Neospec
2
+ class Spec
3
+ class Result
4
+ attr_accessor :expectations, :failures, :start, :finish
5
+
6
+ def initialize
7
+ @expectations = 0
8
+ @failures = []
9
+ @start = nil
10
+ @finish = nil
11
+ end
12
+
13
+ def successful?
14
+ @failures.empty?
15
+ end
16
+
17
+ def start!
18
+ @start = Time.now
19
+ end
20
+
21
+ def finish!
22
+ @finish = Time.now
23
+ end
24
+
25
+ def duration
26
+ return if @start.nil? || @finish.nil?
27
+ @finish - @start
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,66 @@
1
+ class Neospec
2
+ class Spec
3
+ COMMANDS = %w[Given And But When Then]
4
+
5
+ def initialize(description:, block:)
6
+ # Everything here is prefixed to prevent people's specs overriding it,
7
+ # this came to be because I've already accidentally done this a few
8
+ # times.
9
+ @__result = Neospec::Spec::Result.new
10
+ @__description = description
11
+ @__block = block
12
+ end
13
+
14
+ def description
15
+ @__description
16
+ end
17
+
18
+ def result
19
+ @__result
20
+ end
21
+
22
+ def duration
23
+ @__result.duration
24
+ end
25
+
26
+ def successful?
27
+ @__result.successful?
28
+ end
29
+
30
+ def expectations
31
+ @__result.expectations
32
+ end
33
+
34
+ def failures
35
+ @__result.failures
36
+ end
37
+
38
+ def log(message, context: nil, result: nil)
39
+ @__logger.log(message, context: context, result: result)
40
+ end
41
+
42
+ def run(logger:)
43
+ @__logger = logger
44
+ instance_exec { log(@__description, context: :describe) }
45
+ result.start!
46
+ instance_exec(&@__block)
47
+ result.finish!
48
+ end
49
+
50
+ COMMANDS.each do |command|
51
+ define_method(command) do |description, &block|
52
+ log(description, context: command.to_sym)
53
+ block.call
54
+ end
55
+ end
56
+
57
+ def expect(value)
58
+ Neospec::Expector.new(
59
+ result: @__result,
60
+ actual: value,
61
+ stack: caller,
62
+ logger: @__logger
63
+ )
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,28 @@
1
+ class Neospec
2
+ class Suite
3
+ attr_accessor :specs, :runner
4
+
5
+ def initialize(runner: Neospec::Runner::Basic.new)
6
+ @runner = runner
7
+ @specs = []
8
+ end
9
+
10
+ def results
11
+ @specs.map(&:result)
12
+ end
13
+
14
+ def describe(description, &block)
15
+ @specs << Neospec::Spec.new(
16
+ description: description,
17
+ block: block
18
+ )
19
+ end
20
+
21
+ def run(logger:)
22
+ runner.run(
23
+ logger: logger,
24
+ suite: self
25
+ )
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ class Neospec
2
+ VERSION = "0.0.1"
3
+ end