1#!/usr/bin/python
2
3"""
4Copyright 2014 Google Inc.
5
6Use of this source code is governed by a BSD-style license that can be
7found in the LICENSE file.
8
9Test imagepairset.py
10"""
11
12# System-level imports
13import unittest
14
15# Local imports
16import column
17import imagepair
18import imagepairset
19
20
21BASE_URL_1 = 'http://base/url/1'
22BASE_URL_2 = 'http://base/url/2'
23DIFF_BASE_URL = 'http://diff/base/url'
24IMAGEPAIR_1_AS_DICT = {
25    imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS: {
26        'builder': 'MyBuilder',
27        'test': 'test1',
28    },
29    imagepair.KEY__IMAGEPAIRS__IMAGE_A_URL: 'test1/1111.png',
30    imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL: 'test1/1111.png',
31    imagepair.KEY__IMAGEPAIRS__IS_DIFFERENT: False,
32}
33IMAGEPAIR_2_AS_DICT = {
34    imagepair.KEY__IMAGEPAIRS__DIFFERENCES: {
35        'maxDiffPerChannel': [1, 2, 3],
36        'numDifferingPixels': 111,
37        'percentDifferingPixels': 22.222,
38    },
39    imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS: {
40        'builder': 'MyBuilder',
41        'test': 'test2',
42    },
43    imagepair.KEY__IMAGEPAIRS__IMAGE_A_URL: 'test2/2222.png',
44    imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL: 'test2/22223.png',
45    imagepair.KEY__IMAGEPAIRS__IS_DIFFERENT: True,
46}
47IMAGEPAIR_3_AS_DICT = {
48    imagepair.KEY__IMAGEPAIRS__DIFFERENCES: {
49        'maxDiffPerChannel': [4, 5, 6],
50        'numDifferingPixels': 111,
51        'percentDifferingPixels': 44.444,
52    },
53    imagepair.KEY__IMAGEPAIRS__EXPECTATIONS: {
54        'bugs': [1001, 1002],
55        'ignoreFailure': True,
56    },
57    imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS: {
58        'builder': 'MyBuilder',
59        'test': 'test3',
60    },
61    imagepair.KEY__IMAGEPAIRS__IMAGE_A_URL: 'test3/3333.png',
62    imagepair.KEY__IMAGEPAIRS__IMAGE_B_URL: 'test3/33334.png',
63    imagepair.KEY__IMAGEPAIRS__IS_DIFFERENT: True,
64}
65SET_A_DESCRIPTION = 'expectations'
66SET_B_DESCRIPTION = 'actuals'
67
68
69class ImagePairSetTest(unittest.TestCase):
70
71  def setUp(self):
72    self.maxDiff = None  # do not truncate diffs when tests fail
73
74  def shortDescription(self):
75    """Tells unittest framework to not print docstrings for test cases."""
76    return None
77
78  def test_success(self):
79    """Assembles some ImagePairs into an ImagePairSet, and validates results.
80    """
81    image_pairs = [
82        MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1,
83                      dict_to_return=IMAGEPAIR_1_AS_DICT),
84        MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1,
85                      dict_to_return=IMAGEPAIR_2_AS_DICT),
86        MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1,
87                      dict_to_return=IMAGEPAIR_3_AS_DICT),
88    ]
89    expected_imageset_dict = {
90        'extraColumnHeaders': {
91            'builder': {
92                'headerText': 'builder',
93                'isFilterable': True,
94                'isSortable': True,
95                'useFreeformFilter': False,
96                'valuesAndCounts': [('MyBuilder', 3)],
97            },
98            'test': {
99                'headerText': 'which GM test',
100                'headerUrl': 'http://learn/about/gm/tests',
101                'isFilterable': True,
102                'isSortable': False,
103                'useFreeformFilter': False,
104                'valuesAndCounts': [('test1', 1),
105                                    ('test2', 1),
106                                    ('test3', 1)],
107            },
108        },
109        'extraColumnOrder': ['builder', 'test'],
110        'imagePairs': [
111            IMAGEPAIR_1_AS_DICT,
112            IMAGEPAIR_2_AS_DICT,
113            IMAGEPAIR_3_AS_DICT,
114        ],
115        'imageSets': {
116            'imageA': {
117                'baseUrl': BASE_URL_1,
118                'description': SET_A_DESCRIPTION,
119            },
120            'imageB': {
121                'baseUrl': BASE_URL_1,
122                'description': SET_B_DESCRIPTION,
123            },
124            'diffs': {
125                'baseUrl': DIFF_BASE_URL + '/diffs',
126                'description': 'color difference per channel',
127            },
128            'whiteDiffs': {
129                'baseUrl': DIFF_BASE_URL + '/whitediffs',
130                'description': 'differing pixels in white',
131            },
132        },
133    }
134
135    image_pair_set = imagepairset.ImagePairSet(
136        descriptions=(SET_A_DESCRIPTION, SET_B_DESCRIPTION),
137        diff_base_url=DIFF_BASE_URL)
138    for image_pair in image_pairs:
139      image_pair_set.add_image_pair(image_pair)
140    # The 'builder' column header uses the default settings,
141    # but the 'test' column header has manual adjustments.
142    image_pair_set.set_column_header_factory(
143        'test',
144        column.ColumnHeaderFactory(
145            header_text='which GM test',
146            header_url='http://learn/about/gm/tests',
147            is_filterable=True,
148            is_sortable=False))
149    self.assertEqual(image_pair_set.as_dict(), expected_imageset_dict)
150
151  def test_mismatched_base_url(self):
152    """Confirms that mismatched base_urls will cause an exception."""
153    image_pair_set = imagepairset.ImagePairSet(
154        diff_base_url=DIFF_BASE_URL)
155    image_pair_set.add_image_pair(
156        MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1,
157                      dict_to_return=IMAGEPAIR_1_AS_DICT))
158    image_pair_set.add_image_pair(
159        MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1,
160                      dict_to_return=IMAGEPAIR_2_AS_DICT))
161    with self.assertRaises(Exception):
162      image_pair_set.add_image_pair(
163          MockImagePair(imageA_base_url=BASE_URL_2, imageB_base_url=BASE_URL_2,
164                        dict_to_return=IMAGEPAIR_3_AS_DICT))
165
166  def test_missing_column_ids(self):
167    """Confirms that passing truncated column_ids_in_order to as_dict()
168    will cause an exception."""
169    image_pair_set = imagepairset.ImagePairSet(
170        diff_base_url=DIFF_BASE_URL)
171    image_pair_set.add_image_pair(
172        MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1,
173                      dict_to_return=IMAGEPAIR_1_AS_DICT))
174    image_pair_set.add_image_pair(
175        MockImagePair(imageA_base_url=BASE_URL_1, imageB_base_url=BASE_URL_1,
176                      dict_to_return=IMAGEPAIR_2_AS_DICT))
177    # Call as_dict() with default or reasonable column_ids_in_order.
178    image_pair_set.as_dict()
179    image_pair_set.as_dict(column_ids_in_order=['test', 'builder'])
180    image_pair_set.as_dict(column_ids_in_order=['test', 'builder', 'extra'])
181    # Call as_dict() with not enough column_ids.
182    with self.assertRaises(Exception):
183      image_pair_set.as_dict(column_ids_in_order=['builder'])
184
185
186class MockImagePair(object):
187  """Mock ImagePair object, which will return canned results."""
188  def __init__(self, imageA_base_url, imageB_base_url, dict_to_return):
189    """
190    Args:
191      base_url: base_url attribute for this object
192      dict_to_return: dictionary to return from as_dict()
193    """
194    self.imageA_base_url = imageA_base_url
195    self.imageB_base_url = imageB_base_url
196    self.extra_columns_dict = dict_to_return.get(
197        imagepair.KEY__IMAGEPAIRS__EXTRACOLUMNS, None)
198    self._dict_to_return = dict_to_return
199
200  def as_dict(self):
201    return self._dict_to_return
202
203
204def main():
205  suite = unittest.TestLoader().loadTestsFromTestCase(ImagePairSetTest)
206  unittest.TextTestRunner(verbosity=2).run(suite)
207
208
209if __name__ == '__main__':
210  main()
211