diffux_ci 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/diffux_ci/diff_cluster_finder.rb +47 -0
- data/lib/diffux_ci/server.rb +15 -0
- data/lib/diffux_ci/snapshot_comparer.rb +88 -0
- data/lib/diffux_ci/snapshot_comparison_image/after.rb +21 -0
- data/lib/diffux_ci/snapshot_comparison_image/base.rb +107 -0
- data/lib/diffux_ci/snapshot_comparison_image/before.rb +21 -0
- data/lib/diffux_ci/snapshot_comparison_image/gutter.rb +34 -0
- data/lib/diffux_ci/snapshot_comparison_image/overlayed.rb +88 -0
- data/lib/diffux_ci/utils.rb +2 -1
- data/lib/diffux_ci/version.rb +1 -1
- metadata +8 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b444d088ff7fdcfd876e26252d4a9180b7e4aa71
|
4
|
+
data.tar.gz: cfd65df089cee0611a3fe98a6555d1275a3285da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a65d2651905017d4c1a7a032e1483ecfce10c28028c6380ea3a036251d78213dde25e24b7980e001bd7a677288b50eb93f23600ad42535d1869ce48348c0a7f
|
7
|
+
data.tar.gz: 5575fb5c88d9769503a9e8f7d9e796e7e35560dec30f5f064334c6b4332cff989cdee8d887147a77d7bf64a36ec32ac3c62c2250538322c1d6339eca7171d6bf
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'set'
|
2
|
+
module Diffux
|
3
|
+
# This class finds clusters in a diff. A cluster is defined as rows that are
|
4
|
+
# different, and are closer than DIFF_ROW_THRESHOLD pixels to its neighboring
|
5
|
+
# diff row.
|
6
|
+
class DiffClusterFinder
|
7
|
+
MAXIMUM_ADJACENCY_GAP = 5
|
8
|
+
|
9
|
+
# @param number_of_rows [Numeric]
|
10
|
+
def initialize(number_of_rows)
|
11
|
+
@number_of_rows = number_of_rows
|
12
|
+
@rows_with_diff = SortedSet.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Tell the DiffClusterFinder about a row that is different.
|
16
|
+
#
|
17
|
+
# @param row [Numeric]
|
18
|
+
def row_is_different(row)
|
19
|
+
@rows_with_diff.add row
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Float] the percent of rows that are different
|
23
|
+
def percent_of_rows_different
|
24
|
+
@rows_with_diff.length.to_f / @number_of_rows * 100
|
25
|
+
end
|
26
|
+
|
27
|
+
# Calculate clusters from diff-rows that are close to each other.
|
28
|
+
#
|
29
|
+
# @return [Array<Hash>] a list of clusters modeled as hashes:
|
30
|
+
# `{ start: x, finish: y }`
|
31
|
+
def clusters
|
32
|
+
results = []
|
33
|
+
@rows_with_diff.each do |row|
|
34
|
+
current = results.last
|
35
|
+
if !current || current[:finish] + MAXIMUM_ADJACENCY_GAP < row
|
36
|
+
results << {
|
37
|
+
start: row,
|
38
|
+
finish: row,
|
39
|
+
}
|
40
|
+
else
|
41
|
+
current[:finish] = row
|
42
|
+
end
|
43
|
+
end
|
44
|
+
results
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/diffux_ci/server.rb
CHANGED
@@ -38,6 +38,21 @@ module DiffuxCI
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
get '/*' do
|
42
|
+
config = DiffuxCI::Utils.config
|
43
|
+
file = params[:splat].first
|
44
|
+
if File.exist?(file)
|
45
|
+
send_file file
|
46
|
+
else
|
47
|
+
config['public_directories'].each do |pub_dir|
|
48
|
+
filepath = File.join(Dir.pwd, pub_dir, file)
|
49
|
+
if File.exist?(filepath)
|
50
|
+
send_file filepath
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
41
56
|
post '/reject' do
|
42
57
|
DiffuxCI::Action.new(params[:description], params[:viewport]).reject
|
43
58
|
redirect back
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'oily_png'
|
2
|
+
require 'diff-lcs'
|
3
|
+
require_relative 'diff_cluster_finder'
|
4
|
+
|
5
|
+
module DiffuxCI
|
6
|
+
# This class is responsible for comparing two Snapshots and generating a diff.
|
7
|
+
class SnapshotComparer
|
8
|
+
# @param png_before [ChunkyPNG::Image]
|
9
|
+
# @param png_after [ChunkyPNG::Image]
|
10
|
+
def initialize(png_before, png_after)
|
11
|
+
@png_after = png_after
|
12
|
+
@png_before = png_before
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Hash]
|
16
|
+
def compare!
|
17
|
+
no_diff = {
|
18
|
+
diff_in_percent: 0,
|
19
|
+
diff_image: nil,
|
20
|
+
diff_clusters: []
|
21
|
+
}
|
22
|
+
|
23
|
+
# If these images are totally identical, we don't need to do any more
|
24
|
+
# work.
|
25
|
+
return no_diff if @png_before == @png_after
|
26
|
+
|
27
|
+
array_before = to_array_of_arrays(@png_before)
|
28
|
+
array_after = to_array_of_arrays(@png_after)
|
29
|
+
|
30
|
+
# If the arrays of arrays of colors are identical, we don't need to do any
|
31
|
+
# more work. This might happen if some of the headers are different.
|
32
|
+
return no_diff if array_before == array_after
|
33
|
+
|
34
|
+
sdiff = Diff::LCS.sdiff(array_before, array_after)
|
35
|
+
cluster_finder = DiffClusterFinder.new(sdiff.size)
|
36
|
+
sprite, all_comparisons = initialize_comparison_images(
|
37
|
+
[@png_after.width, @png_before.width].max, sdiff.size)
|
38
|
+
|
39
|
+
sdiff.each_with_index do |row, y|
|
40
|
+
# each row is a Diff::LCS::ContextChange instance
|
41
|
+
all_comparisons.each { |image| image.render_row(y, row) }
|
42
|
+
cluster_finder.row_is_different(y) unless row.unchanged?
|
43
|
+
end
|
44
|
+
|
45
|
+
percent_changed = cluster_finder.percent_of_rows_different
|
46
|
+
{
|
47
|
+
diff_in_percent: percent_changed,
|
48
|
+
diff_image: (sprite if percent_changed > 0),
|
49
|
+
diff_clusters: cluster_finder.clusters,
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @param [ChunkyPNG::Image]
|
56
|
+
# @return [Array<Array<Integer>>]
|
57
|
+
def to_array_of_arrays(chunky_png)
|
58
|
+
array_of_arrays = []
|
59
|
+
chunky_png.height.times do |y|
|
60
|
+
array_of_arrays << chunky_png.row(y)
|
61
|
+
end
|
62
|
+
array_of_arrays
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param canvas [ChunkyPNG::Image] The output image to draw pixels on
|
66
|
+
# @return [Array<SnapshotComparisonImage>]
|
67
|
+
def initialize_comparison_images(width, height)
|
68
|
+
gutter_width = SnapshotComparisonImage::Gutter::WIDTH
|
69
|
+
total_width = (width * 3) + (gutter_width * 3)
|
70
|
+
|
71
|
+
sprite = ChunkyPNG::Image.new(total_width, height)
|
72
|
+
offset, comparison_images = 0, []
|
73
|
+
comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
|
74
|
+
offset += gutter_width
|
75
|
+
comparison_images << SnapshotComparisonImage::Before.new(offset, sprite)
|
76
|
+
offset += width
|
77
|
+
comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
|
78
|
+
offset += gutter_width
|
79
|
+
comparison_images << SnapshotComparisonImage::Overlayed.new(offset, sprite)
|
80
|
+
offset += width
|
81
|
+
comparison_images << SnapshotComparisonImage::Gutter.new(offset, sprite)
|
82
|
+
offset += gutter_width
|
83
|
+
comparison_images << SnapshotComparisonImage::After.new(offset, sprite)
|
84
|
+
|
85
|
+
[sprite, comparison_images]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Diffux
|
2
|
+
module SnapshotComparisonImage
|
3
|
+
# This subclass of `SnapshotComparisonImage` knows how to draw the
|
4
|
+
# representation of the "after" image.
|
5
|
+
class After < SnapshotComparisonImage::Base
|
6
|
+
# @param y [Integer]
|
7
|
+
# @param row [Diff::LCS:ContextChange]
|
8
|
+
def render_changed_row(y, row)
|
9
|
+
render_added_row(y, row)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param y [Integer]
|
13
|
+
# @param row [Diff::LCS:ContextChange]
|
14
|
+
def render_added_row(y, row)
|
15
|
+
row.new_element.each_with_index do |pixel_after, x|
|
16
|
+
render_pixel(x, y, pixel_after)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'chunky_png'
|
2
|
+
module Diffux
|
3
|
+
module SnapshotComparisonImage
|
4
|
+
# This model represents a "comparison image". Basically it's just a wrapper
|
5
|
+
# around a ChunkyPNG image with some nice methods to make life easier in the
|
6
|
+
# world of diffs.
|
7
|
+
#
|
8
|
+
# This model is never persisted.
|
9
|
+
class Base
|
10
|
+
include ChunkyPNG::Color
|
11
|
+
|
12
|
+
BASE_OPACITY = 0.1
|
13
|
+
BASE_ALPHA = (255 * BASE_OPACITY).round
|
14
|
+
BASE_DIFF_ALPHA = BASE_ALPHA * 2
|
15
|
+
|
16
|
+
MAGENTA = ChunkyPNG::Color.from_hex '#b33682'
|
17
|
+
RED = ChunkyPNG::Color.from_hex '#dc322f'
|
18
|
+
GREEN = ChunkyPNG::Color.from_hex '#859900'
|
19
|
+
|
20
|
+
# @param offset [Integer] the x-offset that this comparison image should
|
21
|
+
# use when rendering on the canvas image.
|
22
|
+
# @param canvas [ChunkyPNG::Image] The canvas image to render pixels on.
|
23
|
+
def initialize(offset, canvas)
|
24
|
+
@offset = offset
|
25
|
+
@canvas = canvas
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param y [Integer]
|
29
|
+
# @param row [Diff::LCS:ContextChange]
|
30
|
+
def render_row(y, row)
|
31
|
+
if row.unchanged?
|
32
|
+
render_unchanged_row(y, row)
|
33
|
+
elsif row.deleting?
|
34
|
+
render_deleted_row(y, row)
|
35
|
+
elsif row.adding?
|
36
|
+
render_added_row(y, row)
|
37
|
+
else # changing?
|
38
|
+
render_changed_row(y, row)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param y [Integer]
|
43
|
+
# @param row [Diff::LCS:ContextChange]
|
44
|
+
def render_unchanged_row(y, row)
|
45
|
+
row.new_element.each_with_index do |pixel, x|
|
46
|
+
# Render the unchanged pixel as-is
|
47
|
+
render_pixel(x, y, pixel)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param y [Integer]
|
52
|
+
# @param row [Diff::LCS:ContextChange]
|
53
|
+
def render_changed_row(y, row)
|
54
|
+
# no default implementation
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param y [Integer]
|
58
|
+
# @param row [Diff::LCS:ContextChange]
|
59
|
+
def render_added_row(y, row)
|
60
|
+
# no default implementation
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param y [Integer]
|
64
|
+
# @param row [Diff::LCS:ContextChange]
|
65
|
+
def render_deleted_row(y, row)
|
66
|
+
# no default implementation
|
67
|
+
end
|
68
|
+
|
69
|
+
# Compute a score that represents the difference between 2 pixels
|
70
|
+
#
|
71
|
+
# This method simply takes the Euclidean distance between the RGBA channels
|
72
|
+
# of 2 colors over the maximum possible Euclidean distance. This gives us a
|
73
|
+
# percentage of how different the two colors are.
|
74
|
+
#
|
75
|
+
# Although it would be more perceptually accurate to calculate a proper
|
76
|
+
# Delta E in Lab colorspace, we probably don't need perceptual accuracy for
|
77
|
+
# this application, and it is nice to avoid the overhead of converting RGBA
|
78
|
+
# to Lab.
|
79
|
+
#
|
80
|
+
# @param pixel_after [Integer]
|
81
|
+
# @param pixel_before [Integer]
|
82
|
+
# @return [Float] number between 0 and 1 where 1 is completely different
|
83
|
+
# and 0 is no difference
|
84
|
+
def pixel_diff_score(pixel_after, pixel_before)
|
85
|
+
ChunkyPNG::Color::euclidean_distance_rgba(pixel_after, pixel_before) /
|
86
|
+
ChunkyPNG::Color::MAX_EUCLIDEAN_DISTANCE_RGBA
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param diff_score [Float]
|
90
|
+
# @return [Integer] a number between 0 and 255 that represents the alpha
|
91
|
+
# channel of of the difference
|
92
|
+
def diff_alpha(diff_score)
|
93
|
+
(BASE_DIFF_ALPHA + ((255 - BASE_DIFF_ALPHA) * diff_score)).round
|
94
|
+
end
|
95
|
+
|
96
|
+
# Renders a pixel on the specified x and y position. Uses the offset that
|
97
|
+
# the comparison image has been configured with.
|
98
|
+
#
|
99
|
+
# @param x [Integer]
|
100
|
+
# @param y [Integer]
|
101
|
+
# @param pixel [Integer]
|
102
|
+
def render_pixel(x, y, pixel)
|
103
|
+
@canvas.set_pixel(x + @offset, y, pixel)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Diffux
|
2
|
+
module SnapshotComparisonImage
|
3
|
+
# This subclass of `SnapshotComparisonImage` knows how to draw the
|
4
|
+
# representation of the "before" image.
|
5
|
+
class Before < SnapshotComparisonImage::Base
|
6
|
+
# @param y [Integer]
|
7
|
+
# @param row [Diff::LCS:ContextChange]
|
8
|
+
def render_changed_row(y, row)
|
9
|
+
render_deleted_row(y, row)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param y [Integer]
|
13
|
+
# @param row [Diff::LCS:ContextChange]
|
14
|
+
def render_deleted_row(y, row)
|
15
|
+
row.old_element.each_with_index do |pixel_before, x|
|
16
|
+
render_pixel(x, y, pixel_before)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Diffux
|
2
|
+
module SnapshotComparisonImage
|
3
|
+
# This class renders a gutter-column with a color representing the type of
|
4
|
+
# change that has happened.
|
5
|
+
class Gutter < SnapshotComparisonImage::Base
|
6
|
+
WIDTH = 10
|
7
|
+
GRAY = ChunkyPNG::Color.from_hex '#cccccc'
|
8
|
+
|
9
|
+
def render_row(y, row)
|
10
|
+
WIDTH.times do |x|
|
11
|
+
render_pixel(x, y, gutter_color(row))
|
12
|
+
end
|
13
|
+
# render a two-pixel empty column
|
14
|
+
2.times do |x|
|
15
|
+
render_pixel(WIDTH - 1 - x, y, WHITE)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def gutter_color(row)
|
22
|
+
if row.unchanged?
|
23
|
+
WHITE
|
24
|
+
elsif row.deleting?
|
25
|
+
RED
|
26
|
+
elsif row.adding?
|
27
|
+
GREEN
|
28
|
+
else # changed?
|
29
|
+
GRAY
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Diffux
|
2
|
+
module SnapshotComparisonImage
|
3
|
+
# This subclass of `SnapshotComparisonImage` knows how to overlay the
|
4
|
+
# after-image on top of the before-image, and render the difference in a
|
5
|
+
# scaled magenta color.
|
6
|
+
class Overlayed < SnapshotComparisonImage::Base
|
7
|
+
WHITE_OVERLAY = ChunkyPNG::Color.fade(WHITE, 1 - BASE_ALPHA)
|
8
|
+
|
9
|
+
# @param offset [Integer]
|
10
|
+
# @param canvas [ChunkyPNG::Image]
|
11
|
+
# @see SnapshotComparisonImage::Base
|
12
|
+
def initialize(offset, canvas)
|
13
|
+
@diff_pixels = {}
|
14
|
+
@faded_pixels = {}
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param y [Integer]
|
19
|
+
# @param row [Diff::LCS:ContextChange]
|
20
|
+
def render_unchanged_row(y, row)
|
21
|
+
# Render translucent original pixels
|
22
|
+
row.new_element.each_with_index do |pixel, x|
|
23
|
+
render_faded_pixel(x, y, pixel)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param y [Integer]
|
28
|
+
# @param row [Diff::LCS:ContextChange]
|
29
|
+
def render_deleted_row(y, row)
|
30
|
+
row.old_element.each_with_index do |pixel_before, x|
|
31
|
+
render_faded_magenta_pixel(TRANSPARENT, pixel_before, x, y)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param y [Integer]
|
36
|
+
# @param row [Diff::LCS:ContextChange]
|
37
|
+
def render_added_row(y, row)
|
38
|
+
row.new_element.each_with_index do |pixel_after, x|
|
39
|
+
render_faded_magenta_pixel(pixel_after, TRANSPARENT, x, y)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param y [Integer]
|
44
|
+
# @param row [Diff::LCS:ContextChange]
|
45
|
+
def render_changed_row(y, row)
|
46
|
+
row.old_element.zip(row.new_element).each_with_index do |pixels, x|
|
47
|
+
pixel_before, pixel_after = pixels
|
48
|
+
render_faded_magenta_pixel(
|
49
|
+
pixel_after || TRANSPARENT,
|
50
|
+
pixel_before || TRANSPARENT,
|
51
|
+
x, y)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @param pixel_after [Integer]
|
58
|
+
# @param pixel_before [Integer]
|
59
|
+
# @param x [Integer]
|
60
|
+
# @param y [Integer]
|
61
|
+
def render_faded_magenta_pixel(pixel_after, pixel_before, x, y)
|
62
|
+
score = pixel_diff_score(pixel_after, pixel_before)
|
63
|
+
if score > 0
|
64
|
+
render_diff_pixel(x, y, score)
|
65
|
+
else
|
66
|
+
render_faded_pixel(x, y, pixel_after)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param x [Integer]
|
71
|
+
# @param y [Integer]
|
72
|
+
# @param score [Float]
|
73
|
+
def render_diff_pixel(x, y, score)
|
74
|
+
@diff_pixels[score] ||= compose_quick(fade(MAGENTA, diff_alpha(score)),
|
75
|
+
WHITE)
|
76
|
+
render_pixel(x, y, @diff_pixels[score])
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param x [Integer]
|
80
|
+
# @param y [Integer]
|
81
|
+
# @param pixel [Integer]
|
82
|
+
def render_faded_pixel(x, y, pixel)
|
83
|
+
@faded_pixels[pixel] ||= compose_quick(WHITE_OVERLAY, pixel)
|
84
|
+
render_pixel(x, y, @faded_pixels[pixel])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/diffux_ci/utils.rb
CHANGED
@@ -10,6 +10,7 @@ module DiffuxCI
|
|
10
10
|
'snapshots_folder' => './snapshots',
|
11
11
|
'source_files' => [],
|
12
12
|
'stylesheets' => [],
|
13
|
+
'public_directories' => [],
|
13
14
|
'port' => 4567,
|
14
15
|
'driver' => :firefox,
|
15
16
|
'viewports' => {
|
@@ -35,7 +36,7 @@ module DiffuxCI
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def self.normalize_description(description)
|
38
|
-
Base64.
|
39
|
+
Base64.strict_encode64(description).strip
|
39
40
|
end
|
40
41
|
|
41
42
|
def self.path_to(description, viewport_name, file_name)
|
data/lib/diffux_ci/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diffux_ci
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henric Trotzig
|
@@ -137,12 +137,19 @@ files:
|
|
137
137
|
- bin/diffux
|
138
138
|
- lib/diffux_ci.rb
|
139
139
|
- lib/diffux_ci/action.rb
|
140
|
+
- lib/diffux_ci/diff_cluster_finder.rb
|
140
141
|
- lib/diffux_ci/diffs.html.erb
|
141
142
|
- lib/diffux_ci/logger.rb
|
142
143
|
- lib/diffux_ci/public/diffux_ci-runner.js
|
143
144
|
- lib/diffux_ci/public/diffux_ci-styles.css
|
144
145
|
- lib/diffux_ci/runner.rb
|
145
146
|
- lib/diffux_ci/server.rb
|
147
|
+
- lib/diffux_ci/snapshot_comparer.rb
|
148
|
+
- lib/diffux_ci/snapshot_comparison_image/after.rb
|
149
|
+
- lib/diffux_ci/snapshot_comparison_image/base.rb
|
150
|
+
- lib/diffux_ci/snapshot_comparison_image/before.rb
|
151
|
+
- lib/diffux_ci/snapshot_comparison_image/gutter.rb
|
152
|
+
- lib/diffux_ci/snapshot_comparison_image/overlayed.rb
|
146
153
|
- lib/diffux_ci/uploader.rb
|
147
154
|
- lib/diffux_ci/utils.rb
|
148
155
|
- lib/diffux_ci/version.rb
|