1 #!/usr/bin/env python 2 # Copyright (C) 2010 Google Inc. All rights reserved. 3 # 4 # Redistribution and use in source and binary forms, with or without 5 # modification, are permitted provided that the following conditions 6 # are met: 7 # 8 # 1. Redistributions of source code must retain the above copyright 9 # notice, this list of conditions and the following disclaimer. 10 # 2. Redistributions in binary form must reproduce the above copyright 11 # notice, this list of conditions and the following disclaimer in the 12 # documentation and/or other materials provided with the distribution. 13 # 14 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 15 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 18 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 25 import os 26 import re 27 28 29 class NaiveImageDiffer(object): 30 def same_image(self, img1, img2): 31 return img1 == img2 32 33 34 class TestOutput(object): 35 """Represents the output that a single layout test generates when it is run 36 on a particular platform. 37 Note that this is the raw output that is produced when the layout test is 38 run, not the results of the subsequent comparison between that output and 39 the expected output.""" 40 def __init__(self, platform, output_type, files): 41 self._output_type = output_type 42 self._files = files 43 file = files[0] # Pick some file to do test name calculation. 44 self._name = self._extract_test_name(file.name()) 45 self._is_actual = '-actual.' in file.name() 46 47 self._platform = platform or self._extract_platform(file.name()) 48 49 def _extract_platform(self, filename): 50 """Calculates the platform from the name of the file if it isn't known already""" 51 path = filename.split(os.path.sep) 52 if 'platform' in path: 53 return path[path.index('platform') + 1] 54 return None 55 56 def _extract_test_name(self, filename): 57 path = filename.split(os.path.sep) 58 if 'LayoutTests' in path: 59 path = path[1 + path.index('LayoutTests'):] 60 if 'layout-test-results' in path: 61 path = path[1 + path.index('layout-test-results'):] 62 if 'platform' in path: 63 path = path[2 + path.index('platform'):] 64 65 filename = path[-1] 66 filename = re.sub('-expected\..*$', '', filename) 67 filename = re.sub('-actual\..*$', '', filename) 68 path[-1] = filename 69 return os.path.sep.join(path) 70 71 def save_to(self, path): 72 """Have the files in this TestOutput write themselves to the disk at the specified location.""" 73 for file in self._files: 74 file.save_to(path) 75 76 def is_actual(self): 77 """Is this output the actual output of a test? (As opposed to expected output.)""" 78 return self._is_actual 79 80 def name(self): 81 """The name of this test (doesn't include extension)""" 82 return self._name 83 84 def __eq__(self, other): 85 return (other != None and 86 self.name() == other.name() and 87 self.type() == other.type() and 88 self.platform() == other.platform() and 89 self.is_actual() == other.is_actual() and 90 self.same_content(other)) 91 92 def __hash__(self): 93 return hash(str(self.name()) + str(self.type()) + str(self.platform())) 94 95 def is_new_baseline_for(self, other): 96 return (self.name() == other.name() and 97 self.type() == other.type() and 98 self.platform() == other.platform() and 99 self.is_actual() and 100 (not other.is_actual())) 101 102 def __str__(self): 103 actual_str = '[A] ' if self.is_actual() else '' 104 return "TestOutput[%s/%s] %s%s" % (self._platform, self._output_type, actual_str, self.name()) 105 106 def type(self): 107 return self._output_type 108 109 def platform(self): 110 return self._platform 111 112 def _path_to_platform(self): 113 """Returns the path that tests for this platform are stored in.""" 114 if self._platform is None: 115 return "" 116 else: 117 return os.path.join("self._platform", self._platform) 118 119 def _save_expected_result(self, file, path): 120 path = os.path.join(path, self._path_to_platform()) 121 extension = os.path.splitext(file.name())[1] 122 filename = self.name() + '-expected' + extension 123 file.save_to(path, filename) 124 125 def save_expected_results(self, path_to_layout_tests): 126 """Save the files of this TestOutput to the appropriate directory 127 inside the LayoutTests directory. Typically this means that these files 128 will be saved in "LayoutTests/platform/<platform>/, or simply 129 LayoutTests if the platform is None.""" 130 for file in self._files: 131 self._save_expected_result(file, path_to_layout_tests) 132 133 def delete(self): 134 """Deletes the files that comprise this TestOutput from disk. This 135 fails if the files are virtual files (eg: the files may reside inside a 136 remote zip file).""" 137 for file in self._files: 138 file.delete() 139 140 141 class TextTestOutput(TestOutput): 142 """Represents a text output of a single test on a single platform""" 143 def __init__(self, platform, text_file): 144 self._text_file = text_file 145 TestOutput.__init__(self, platform, 'text', [text_file]) 146 147 def same_content(self, other): 148 return self._text_file.contents() == other._text_file.contents() 149 150 def retarget(self, platform): 151 return TextTestOutput(platform, self._text_file) 152 153 154 class ImageTestOutput(TestOutput): 155 image_differ = NaiveImageDiffer() 156 """Represents an image output of a single test on a single platform""" 157 def __init__(self, platform, image_file, checksum_file): 158 self._checksum_file = checksum_file 159 self._image_file = image_file 160 files = filter(bool, [self._checksum_file, self._image_file]) 161 TestOutput.__init__(self, platform, 'image', files) 162 163 def has_checksum(self): 164 return self._checksum_file is not None 165 166 def same_content(self, other): 167 # FIXME This should not assume that checksums are up to date. 168 if self.has_checksum() and other.has_checksum(): 169 return self._checksum_file.contents() == other._checksum_file.contents() 170 else: 171 self_contents = self._image_file.contents() 172 other_contents = other._image_file.contents() 173 return ImageTestOutput.image_differ.same_image(self_contents, other_contents) 174 175 def retarget(self, platform): 176 return ImageTestOutput(platform, self._image_file, self._checksum_file) 177 178 def checksum(self): 179 return self._checksum_file.contents() 180