1require 'rubygems' 2# Set up gems listed in the Gemfile. 3ENV['BUNDLE_GEMFILE'] ||= File.expand_path('Gemfile', File.dirname(__FILE__)) 4require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 5Bundler.require(:default, :test) if defined?(Bundler) 6 7require File.expand_path('nginx_configuration', File.dirname(__FILE__)) 8 9RSpec.configure do |config| 10 config.after(:each) do 11 NginxTestHelper::Config.delete_config_and_log_files(config_id) if has_passed? 12 end 13 config.order = "random" 14 config.run_all_when_everything_filtered = true 15end 16 17RSpec::Matchers.define :be_perceptual_equal_to do |expected, accuracy=99| 18 match do |actual| 19 (Pixmap.from_jpeg_buffer(actual) - Pixmap.from_jpeg_file(expected)).percentage_pixels_non_zero < (100 - accuracy) 20 end 21 22 failure_message do |actual| 23 "expected that #{actual} would be #{accuracy}% equals to #{expected}" 24 end 25 26 failure_message_when_negated do |actual| 27 "expected that #{actual} would be #{accuracy}% different from #{expected}" 28 end 29 30 description do 31 "be #{accuracy}% equals" 32 end 33end 34 35def image(url, headers={}, expected_status="200") 36 uri = URI.parse(nginx_address + url) 37 the_response = Net::HTTP.start(uri.host, uri.port) do |http| 38 http.read_timeout = 120 39 http.get(uri.request_uri, headers) 40 end 41 42 expect(the_response.code).to eq(expected_status) 43 if the_response.code == "200" 44 expect(the_response.header.content_type).to eq("image/jpeg") 45 the_response.body 46 else 47 expect(the_response.header.content_type).to eq("text/html") 48 nil 49 end 50end 51 52class Pixmap 53 def initialize(width, height) 54 @width = width 55 @height = height 56 @data = fill(RGBColour::WHITE) 57 end 58 59 attr_reader :width, :height 60 61 def fill(colour) 62 @data = Array.new(@width) {Array.new(@height, colour)} 63 end 64 65 def [](x, y) 66 validate_pixel(x,y) 67 @data[x][y] 68 end 69 alias_method :get_pixel, :[] 70 71 def []=(x, y, colour) 72 validate_pixel(x,y) 73 @data[x][y] = colour 74 end 75 alias_method :set_pixel, :[]= 76 77 def each_pixel 78 if block_given? 79 @height.times {|y| @width.times {|x| yield x,y }} 80 else 81 to_enum(:each_pixel) 82 end 83 end 84 85 # the difference between two images 86 def -(a_pixmap) 87 if @width != a_pixmap.width or @height != a_pixmap.height 88 raise ArgumentError, "can't compare images with different sizes" 89 end 90 91 bitmap = self.class.new(@width, @height) 92 bitmap.each_pixel do |x, y| 93 bitmap[x, y] = self[x, y] - a_pixmap[x, y] 94 end 95 bitmap 96 end 97 98 def percentage_pixels_non_zero 99 sum = 0 100 each_pixel {|x,y| sum += self[x, y].values.inject(0) {|colors_sum, val| colors_sum + val }} 101 100.0 * Float(sum) / (@width * @height * 255 * 3) 102 end 103 104 def self.from_jpeg_file(filename) 105 unless File.readable?(filename) 106 raise ArgumentError, "#{filename} does not exists or is not readable." 107 end 108 109 jpeg_to_pixmap(Jpeg.open(filename)) 110 end 111 112 def self.from_jpeg_buffer(buffer) 113 jpeg_to_pixmap(Jpeg.open_buffer(buffer)) 114 end 115 116 private 117 118 def self.jpeg_to_pixmap(jpeg) 119 width = jpeg.width 120 height = jpeg.height 121 122 if width < 1 || height < 1 123 raise StandardError, "file '#{filename}' does not start with the expected header" 124 end 125 126 raw_data = jpeg.raw_data 127 bitmap = self.new(width, height) 128 bitmap.each_pixel do |x,y| 129 values = raw_data[y][x] 130 if jpeg.rgb? 131 bitmap[x,y] = RGBColour.new(values[0], values[1], values[2]) 132 else 133 bitmap[x,y] = RGBColour.new(values[0], values[0], values[0]) 134 end 135 end 136 bitmap 137 end 138 139 def validate_pixel(x,y) 140 unless x.between?(0, @width - 1) && y.between?(0, @height - 1) 141 raise ArgumentError, "requested pixel (#{x}, #{y}) is outside dimensions of this bitmap" 142 end 143 end 144end 145 146class RGBColour 147 # Red, green and blue values must fall in the range 0..255. 148 def initialize(red, green, blue) 149 ok = [red, green, blue].inject(true) {|ret, c| ret &= c.between?(0,255)} 150 raise ArgumentError, "invalid RGB parameters: #{[red, green, blue].inspect}" unless ok 151 @red, @green, @blue = red, green, blue 152 end 153 154 attr_reader :red, :green, :blue 155 alias_method :r, :red 156 alias_method :g, :green 157 alias_method :b, :blue 158 159 def values 160 [@red, @green, @blue] 161 end 162 163 def -(a_colour) 164 self.class.new((@red - a_colour.red).abs, (@green - a_colour.green).abs, (@blue - a_colour.blue).abs) 165 end 166 167 WHITE = RGBColour.new(255, 255, 255) 168end 169