quickdraw 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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