#!/usr/bin/env python # Copyright (C) 2010 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import os import re class NaiveImageDiffer(object): def same_image(self, img1, img2): return img1 == img2 class TestOutput(object): """Represents the output that a single layout test generates when it is run on a particular platform. Note that this is the raw output that is produced when the layout test is run, not the results of the subsequent comparison between that output and the expected output.""" def __init__(self, platform, output_type, files): self._output_type = output_type self._files = files file = files[0] # Pick some file to do test name calculation. self._name = self._extract_test_name(file.name()) self._is_actual = '-actual.' in file.name() self._platform = platform or self._extract_platform(file.name()) def _extract_platform(self, filename): """Calculates the platform from the name of the file if it isn't known already""" path = filename.split(os.path.sep) if 'platform' in path: return path[path.index('platform') + 1] return None def _extract_test_name(self, filename): path = filename.split(os.path.sep) if 'LayoutTests' in path: path = path[1 + path.index('LayoutTests'):] if 'layout-test-results' in path: path = path[1 + path.index('layout-test-results'):] if 'platform' in path: path = path[2 + path.index('platform'):] filename = path[-1] filename = re.sub('-expected\..*$', '', filename) filename = re.sub('-actual\..*$', '', filename) path[-1] = filename return os.path.sep.join(path) def save_to(self, path): """Have the files in this TestOutput write themselves to the disk at the specified location.""" for file in self._files: file.save_to(path) def is_actual(self): """Is this output the actual output of a test? (As opposed to expected output.)""" return self._is_actual def name(self): """The name of this test (doesn't include extension)""" return self._name def __eq__(self, other): return (other != None and self.name() == other.name() and self.type() == other.type() and self.platform() == other.platform() and self.is_actual() == other.is_actual() and self.same_content(other)) def __hash__(self): return hash(str(self.name()) + str(self.type()) + str(self.platform())) def is_new_baseline_for(self, other): return (self.name() == other.name() and self.type() == other.type() and self.platform() == other.platform() and self.is_actual() and (not other.is_actual())) def __str__(self): actual_str = '[A] ' if self.is_actual() else '' return "TestOutput[%s/%s] %s%s" % (self._platform, self._output_type, actual_str, self.name()) def type(self): return self._output_type def platform(self): return self._platform def _path_to_platform(self): """Returns the path that tests for this platform are stored in.""" if self._platform is None: return "" else: return os.path.join("self._platform", self._platform) def _save_expected_result(self, file, path): path = os.path.join(path, self._path_to_platform()) extension = os.path.splitext(file.name())[1] filename = self.name() + '-expected' + extension file.save_to(path, filename) def save_expected_results(self, path_to_layout_tests): """Save the files of this TestOutput to the appropriate directory inside the LayoutTests directory. Typically this means that these files will be saved in "LayoutTests/platform//, or simply LayoutTests if the platform is None.""" for file in self._files: self._save_expected_result(file, path_to_layout_tests) def delete(self): """Deletes the files that comprise this TestOutput from disk. This fails if the files are virtual files (eg: the files may reside inside a remote zip file).""" for file in self._files: file.delete() class TextTestOutput(TestOutput): """Represents a text output of a single test on a single platform""" def __init__(self, platform, text_file): self._text_file = text_file TestOutput.__init__(self, platform, 'text', [text_file]) def same_content(self, other): return self._text_file.contents() == other._text_file.contents() def retarget(self, platform): return TextTestOutput(platform, self._text_file) class ImageTestOutput(TestOutput): image_differ = NaiveImageDiffer() """Represents an image output of a single test on a single platform""" def __init__(self, platform, image_file, checksum_file): self._checksum_file = checksum_file self._image_file = image_file files = filter(bool, [self._checksum_file, self._image_file]) TestOutput.__init__(self, platform, 'image', files) def has_checksum(self): return self._checksum_file is not None def same_content(self, other): # FIXME This should not assume that checksums are up to date. if self.has_checksum() and other.has_checksum(): return self._checksum_file.contents() == other._checksum_file.contents() else: self_contents = self._image_file.contents() other_contents = other._image_file.contents() return ImageTestOutput.image_differ.same_image(self_contents, other_contents) def retarget(self, platform): return ImageTestOutput(platform, self._image_file, self._checksum_file) def checksum(self): return self._checksum_file.contents()