quickdraw 0.0.5 → 0.1.0

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 (45) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +18 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +49 -0
  8. data/LICENSE.txt +17 -18
  9. data/README.md +100 -29
  10. data/config/quickdraw.rb +16 -0
  11. data/exe/qt +12 -0
  12. data/lib/quickdraw/cluster.rb +27 -0
  13. data/lib/quickdraw/configuration.rb +13 -0
  14. data/lib/quickdraw/context.rb +138 -0
  15. data/lib/quickdraw/expectation.rb +62 -0
  16. data/lib/quickdraw/map.rb +30 -0
  17. data/lib/quickdraw/matchers/boolean.rb +11 -0
  18. data/lib/quickdraw/matchers/case_equality.rb +9 -0
  19. data/lib/quickdraw/matchers/change.rb +33 -0
  20. data/lib/quickdraw/matchers/equality.rb +39 -0
  21. data/lib/quickdraw/matchers/include.rb +15 -0
  22. data/lib/quickdraw/matchers/predicate.rb +14 -0
  23. data/lib/quickdraw/matchers/respond_to.rb +15 -0
  24. data/lib/quickdraw/matchers/to_be_a.rb +18 -0
  25. data/lib/quickdraw/matchers/to_have_attributes.rb +13 -0
  26. data/lib/quickdraw/matchers/to_raise.rb +26 -0
  27. data/lib/quickdraw/matchers/to_receive.rb +30 -0
  28. data/lib/quickdraw/matchers.rb +13 -0
  29. data/lib/quickdraw/pipe.rb +27 -0
  30. data/lib/quickdraw/queue.rb +32 -0
  31. data/lib/quickdraw/registry.rb +57 -0
  32. data/lib/quickdraw/run.rb +53 -0
  33. data/lib/quickdraw/runner.rb +48 -0
  34. data/lib/quickdraw/timer.rb +30 -0
  35. data/lib/quickdraw/version.rb +3 -1
  36. data/lib/quickdraw/worker.rb +28 -0
  37. data/lib/quickdraw.rb +29 -22
  38. metadata +51 -123
  39. data/.gitignore +0 -17
  40. data/Rakefile +0 -1
  41. data/bin/quickdraw +0 -25
  42. data/lib/quickdraw/cli.rb +0 -251
  43. data/lib/quickdraw/shopify_connector.rb +0 -105
  44. data/lib/quickdraw/shopify_connector_pool.rb +0 -54
  45. data/quickdraw.gemspec +0 -29
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::Boolean
4
+ def to_be_truthy
5
+ assert(value) { "expected `#{value.inspect}` to be truthy" }
6
+ end
7
+
8
+ def to_be_falsy
9
+ refute(value) { "expected `#{value.inspect}` to be falsy" }
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::CaseEquality
4
+ def ===(other)
5
+ assert value === other do
6
+ "expected `#{value.inspect}` to === `#{other.inspect}`"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::Change
4
+ def to_change(from: Quickdraw::Null, to: Quickdraw::Null, by: Quickdraw::Null, &object)
5
+ original = object.call
6
+
7
+ if Quickdraw::Null != from
8
+ assert from === original do
9
+ "expected `#{original.inspect}` to be `#{from.inspect}`"
10
+ end
11
+ end
12
+
13
+ block.call
14
+
15
+ new = object.call
16
+
17
+ if new == original
18
+ failure! "expected `#{original.inspect}` to change"
19
+ end
20
+
21
+ if Quickdraw::Null != to
22
+ assert to === new do
23
+ "expected `#{new.inspect}` to be `#{to.inspect}`"
24
+ end
25
+ end
26
+
27
+ if Quickdraw::Null != by
28
+ assert by === (new - original) do
29
+ "expected `#{new.inspect}` to change by `#{by.inspect}`"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::Equality
4
+ def ==(other)
5
+ assert value == other do
6
+ "expected `#{value.inspect}` to == `#{other.inspect}`"
7
+ end
8
+ end
9
+
10
+ def !=(other)
11
+ assert value != other do
12
+ "expected `#{value.inspect}` to != `#{other.inspect}`"
13
+ end
14
+ end
15
+
16
+ def to_eql?(other)
17
+ assert value.eql?(other) do
18
+ "expected `#{value.inspect}` to eql? `#{other.inspect}`"
19
+ end
20
+ end
21
+
22
+ def not_to_eql?(other)
23
+ refute value.eql?(other) do
24
+ "expected `#{value.inspect}` not to eql? `#{other.inspect}`"
25
+ end
26
+ end
27
+
28
+ def to_equal?(other)
29
+ assert value.equal?(other) do
30
+ "expected `#{value.inspect}` to equal? `#{other.inspect}`"
31
+ end
32
+ end
33
+
34
+ def not_to_equal?(other)
35
+ refute value.equal?(other) do
36
+ "expected `#{value.inspect}` not to equal? `#{other.inspect}`"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::Include
4
+ def to_include(other)
5
+ assert value.include?(other) do
6
+ "expected `#{value.inspect}` to include? `#{other.inspect}`"
7
+ end
8
+ end
9
+
10
+ def not_to_include(other)
11
+ refute value.include?(other) do
12
+ "expected `#{value.inspect}` to not include? `#{other.inspect}`"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::Predicate
4
+ def to_be(predicate)
5
+ assert(value.send(predicate)) { "expected `#{value.inspect}` to be `#{predicate.inspect}`" }
6
+ end
7
+
8
+ def not_to_be(predicate)
9
+ refute(value.send(predicate)) { "expected `#{value.inspect}` to not be `#{predicate.inspect}`" }
10
+ end
11
+
12
+ alias_method :to_have, :to_be
13
+ alias_method :not_to_have, :not_to_be
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::RespondTo
4
+ def to_respond_to(method)
5
+ assert value.respond_to?(method) do
6
+ "expected `#{value.inspect}` to respond to `#{method.inspect}`"
7
+ end
8
+ end
9
+
10
+ def not_to_respond_to(method)
11
+ refute value.respond_to?(method) do
12
+ "expected `#{value.inspect}` to not respond to `#{method.inspect}`"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::ToBeA
4
+ def to_be_a(type)
5
+ assert type === value do
6
+ "expected `#{value.inspect}` to have the type `#{type.inspect}`"
7
+ end
8
+ end
9
+
10
+ def not_to_be_a(type)
11
+ refute type === value do
12
+ "expected `#{value.inspect}` to not have the type `#{type.inspect}`"
13
+ end
14
+ end
15
+
16
+ alias_method :to_be_an, :to_be_a
17
+ alias_method :not_to_be_an, :not_to_be_a
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::ToHaveAttributes
4
+ def to_have_attributes(**attributes)
5
+ attributes.each do |k, v|
6
+ assert v === value.send(k) do
7
+ "expected `#{value.inspect}` to have the attribute `#{k.inspect}` equal to `#{v.inspect}`"
8
+ end
9
+ end
10
+ rescue NoMethodError => e
11
+ failure! { "expected `#{value.inspect}` to respond to `#{e.name}`" }
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::ToRaise
4
+ def to_raise(error = ::Exception)
5
+ expectation_block = block
6
+
7
+ begin
8
+ expectation_block.call
9
+ rescue error => e
10
+ success!
11
+ yield(e) if block_given?
12
+ return
13
+ rescue ::Exception => e
14
+ return failure! { "expected `#{error.inspect}` to be raised but `#{e.class.inspect}` was raised" }
15
+ end
16
+
17
+ failure! { "expected #{error} to be raised but wasn't" }
18
+ end
19
+
20
+ def not_to_raise
21
+ block.call
22
+ success!
23
+ rescue ::Exception => e
24
+ failure! { "expected the block not to raise, but it raised `#{e.class}`" }
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quickdraw::Matchers::ToReceive
4
+ def to_receive(method_name, &expectation_block)
5
+ __raise__ ::ArgumentError, "You can't use the `to_receive` matcher with a block expectation." if @block
6
+
7
+ interceptor = ::Module.new
8
+
9
+ # Define local variables so they're available from the block
10
+ expectation = self
11
+ context = @context
12
+
13
+ interceptor.define_method(method_name) do |*args, **kwargs, &block|
14
+ expectation.success!
15
+ super_block = -> (*a, &b) { (a.length > 0) || b ? super(*a, &b) : super(*args, **kwargs, &block) }
16
+ original_super = context.instance_variable_get(:@super)
17
+ begin
18
+ context.instance_variable_set(:@super, super_block)
19
+ result = expectation_block&.call(*args, **kwargs, &block)
20
+ ensure
21
+ context.instance_variable_set(:@super, original_super)
22
+ end
23
+
24
+ interceptor.define_method(method_name) { |*a, **k, &b| super(*a, **k, &b) }
25
+ result
26
+ end
27
+
28
+ value.singleton_class.prepend(interceptor)
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Quickdraw::Matchers
2
+ autoload :Boolean, "quickdraw/matchers/boolean"
3
+ autoload :CaseEquality, "quickdraw/matchers/case_equality"
4
+ autoload :Change, "quickdraw/matchers/change"
5
+ autoload :Equality, "quickdraw/matchers/equality"
6
+ autoload :Include, "quickdraw/matchers/include"
7
+ autoload :Predicate, "quickdraw/matchers/predicate"
8
+ autoload :RespondTo, "quickdraw/matchers/respond_to"
9
+ autoload :ToBeA, "quickdraw/matchers/to_be_a"
10
+ autoload :ToHaveAttributes, "quickdraw/matchers/to_have_attributes"
11
+ autoload :ToRaise, "quickdraw/matchers/to_raise"
12
+ autoload :ToReceive, "quickdraw/matchers/to_receive"
13
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Quickdraw::Pipe
4
+ def initialize
5
+ @reader, @writer = IO.pipe
6
+ end
7
+
8
+ def reader
9
+ @writer.close
10
+ @reader
11
+ end
12
+
13
+ def writer
14
+ @reader.close
15
+ @writer
16
+ end
17
+
18
+ def with_writer
19
+ @reader.close
20
+ yield(@writer).tap { @writer.close }
21
+ end
22
+
23
+ def with_reader
24
+ @writer.close
25
+ yield(@reader).tap { @reader.close }
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Quickdraw::Queue
4
+ def initialize
5
+ @array = []
6
+ @mutex = Mutex.new
7
+ end
8
+
9
+ def <<(item)
10
+ @mutex.synchronize { @array << item }
11
+ end
12
+
13
+ def drain
14
+ yield(shift) until empty?
15
+ end
16
+
17
+ def pop
18
+ @mutex.synchronize { @array.pop }
19
+ end
20
+
21
+ def shift
22
+ @mutex.synchronize { @array.shift }
23
+ end
24
+
25
+ def empty?
26
+ @array.empty?
27
+ end
28
+
29
+ def size
30
+ @array.size
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ # frozen_string_literal: true
5
+
6
+ class Quickdraw::Registry
7
+ def initialize
8
+ @registered_matchers = Quickdraw::Map.new
9
+ @type_matchers = Quickdraw::Map.new
10
+ @shapes = Quickdraw::Map.new
11
+ end
12
+
13
+ # Register a new matcher for the given types.
14
+ def register(matcher, *types)
15
+ @registered_matchers[matcher] = types
16
+
17
+ # We need to clear this cache because the output of `slowly_find_matchers_for` might have changed.
18
+ @type_matchers.clear
19
+ end
20
+
21
+ # Given a value, find or build an expectation class that includes all the matchers for the value.
22
+ def expectation_for(value, matchers: nil)
23
+ if matchers
24
+ shape_for(
25
+ matchers + matchers_for(value)
26
+ )
27
+ else
28
+ shape_for(
29
+ matchers_for(value)
30
+ )
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # A "shape" is a specialised Expectation class that includes the given matchers. It's cached against the list of matchers.
37
+ def shape_for(matchers)
38
+ @shapes[matchers] ||= Class.new(Quickdraw::Expectation) do
39
+ matchers.each { |m| include m }
40
+ freeze
41
+ end
42
+ end
43
+
44
+ # Given a value, find all the matchers that match it. This is cached against the class of the value.
45
+ def matchers_for(value)
46
+ @type_matchers[value.class] ||= slowly_find_matchers_for(value)
47
+ end
48
+
49
+ # If the above has a cache miss, we'll need to find the correct matchers slowly and then cache them.
50
+ def slowly_find_matchers_for(value)
51
+ [].tap do |matchers|
52
+ @registered_matchers.each do |matcher, types|
53
+ matchers << matcher if types.any? { |t| t === value }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Quickdraw::Run
4
+ def initialize(number_of_processes:, number_of_threads: 1, test_files:)
5
+ @number_of_processes = number_of_processes
6
+ @number_of_threads = number_of_threads
7
+ @test_files = test_files.shuffle
8
+
9
+ @cluster = Quickdraw::Cluster.new
10
+ @batches = Array.new(@number_of_processes) { Quickdraw::Queue.new }
11
+
12
+ @test_files.each_with_index do |file, index|
13
+ @batches[index % @number_of_processes] << file
14
+ end
15
+ end
16
+
17
+ def call
18
+ report_time do
19
+ fork_processes
20
+ @results = @cluster.wait
21
+ puts_results
22
+ end
23
+ end
24
+
25
+ def report_time(&)
26
+ total_time = Quickdraw::Timer.time(&)
27
+ puts "Total time: #{total_time}"
28
+ end
29
+
30
+ def fork_processes
31
+ # Enable YJIT right before forking
32
+ RubyVM::YJIT.enable
33
+
34
+ @batches.each_with_index do |batch, index|
35
+ @cluster.fork do |writer|
36
+ results = @number_of_threads.times.map do
37
+ Thread.new { Quickdraw::Runner.call(batch) }
38
+ end.map(&:value)
39
+
40
+ results.each_with_index do |result, thread|
41
+ writer.write("\n")
42
+ writer.write("Process[#{index + 1}], Thread[#{thread + 1}]: #{result.successes.count} assertions passed in #{result.duration}. #{Quickdraw::SUCCESS_EMOJI.sample}")
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def puts_results
49
+ puts
50
+ puts
51
+ puts "Collated results: \n#{@results.join("\n")}"
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Quickdraw::Runner
4
+ def self.call(queue)
5
+ tests = []
6
+
7
+ queue.drain do |f|
8
+ tests << [f, Class.new(Quickdraw::Context) do
9
+ class_eval(
10
+ File.read(f), f, 1
11
+ )
12
+ end]
13
+ end
14
+
15
+ new(tests).tap(&:call)
16
+ end
17
+
18
+ def initialize(tests = nil)
19
+ @tests = tests
20
+
21
+ @successes = []
22
+ @failures = []
23
+ end
24
+
25
+ attr_reader :successes, :failures, :duration
26
+
27
+ def call
28
+ @duration = Quickdraw::Timer.time do
29
+ @tests.each { |(f, t)| t.run(self, [f]) }
30
+ end
31
+ end
32
+
33
+ def success!(name)
34
+ @successes << [name]
35
+
36
+ Kernel.print "🟢 "
37
+ # ::Kernel.print "\e[32m⚬\e[0m"
38
+ end
39
+
40
+ def failure!(path, &message)
41
+ location = caller_locations.drop_while { |l| !l.path.include?(".test.rb") }
42
+
43
+ @failures << [message, location, path]
44
+
45
+ Kernel.print "🔴 "
46
+ # ::Kernel.print "\e[31m⚬\e[0m"
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Quickdraw::Timer
4
+ class Duration
5
+ def initialize(duration)
6
+ @duration = duration
7
+ end
8
+
9
+ def to_s
10
+ if @duration < 1_000
11
+ "#{@duration}ns"
12
+ elsif @duration < 1_000_000
13
+ "#{@duration / 1_000}μs"
14
+ elsif @duration < 1_000_000_000
15
+ "#{@duration / 1_000_000}ms"
16
+ elsif @duration < 60_000_000_000
17
+ "#{@duration / 1_000_000_000}s"
18
+ else
19
+ "#{(@duration / 60_000_000_000)}m"
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.time
25
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
26
+ yield
27
+ finish = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
28
+ Duration.new(finish - start)
29
+ end
30
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Quickdraw
2
- VERSION = "0.0.5"
4
+ VERSION = "0.1.0"
3
5
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Quickdraw::Worker
4
+ def self.fork
5
+ pipe = Quickdraw::Pipe.new
6
+
7
+ pid = Process.fork do
8
+ pipe.with_writer do |writer|
9
+ yield(writer)
10
+ end
11
+ end
12
+
13
+ new(pid:, pipe:)
14
+ end
15
+
16
+ def initialize(pid:, pipe:)
17
+ @pid = pid
18
+ @pipe = pipe
19
+ end
20
+
21
+ def wait
22
+ Process.wait(@pid)
23
+
24
+ @pipe.with_reader do |reader|
25
+ reader.read
26
+ end
27
+ end
28
+ end
data/lib/quickdraw.rb CHANGED
@@ -1,33 +1,40 @@
1
- require "quickdraw/version"
2
- require 'pathname'
3
- require 'filepath'
1
+ # frozen_string_literal: true
4
2
 
5
3
  module Quickdraw
4
+ autoload :Cluster, "quickdraw/cluster"
5
+ autoload :Configuration, "quickdraw/configuration"
6
+ autoload :Context, "quickdraw/context"
7
+ autoload :Expectation, "quickdraw/expectation"
8
+ autoload :Matchers, "quickdraw/matchers"
9
+ autoload :Pipe, "quickdraw/pipe"
10
+ autoload :Registry, "quickdraw/registry"
11
+ autoload :Run, "quickdraw/run"
12
+ autoload :Runner, "quickdraw/runner"
13
+ autoload :Timer, "quickdraw/timer"
14
+ autoload :Worker, "quickdraw/worker"
15
+ autoload :Queue, "quickdraw/queue"
16
+ autoload :Map, "quickdraw/map"
6
17
 
7
- NOOPParser = Proc.new {|data, format| {} }
8
- TIMER_RESET = 5 * 60 + 5
9
- PERMIT_LOWER_LIMIT = 10
18
+ SUCCESS_EMOJI = %w[💃 🕺 🎉 🎊 💪 👏 🙌 ✨ 🥳 🎈 🌈 🎯 🏆]
10
19
 
11
- def self.config
12
- @config ||= if File.exist? 'config.yml'
13
- config = YAML.load(File.read('config.yml'))
14
- config
15
- else
16
- puts "config.yml does not exist!"
17
- {}
18
- end
19
- end
20
+ Null = Object.new.freeze
21
+ Config = Configuration.new
20
22
 
21
- def self.getwd
22
- FilePath.getwd
23
- end
23
+ module Error; end
24
24
 
25
- def self.theme_dir
26
- FilePath.getwd / 'theme'
25
+ class TestFailure < RuntimeError
26
+ include Error
27
27
  end
28
28
 
29
- def self.src_dir
30
- FilePath.getwd / 'src'
29
+ class ArgumentError < ::ArgumentError
30
+ include Error
31
31
  end
32
32
 
33
+ def self.configure(&block)
34
+ if block.arity == 0
35
+ Config.instance_eval(&block)
36
+ else
37
+ yield Config
38
+ end
39
+ end
33
40
  end