blob: 053291af9a6caf71297475ab5c50b8e7a1bdcaa1 [file] [log] [blame]
zachr@google.com48b88912013-07-25 19:49:17 +00001#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4from __future__ import print_function
zachr@google.com785ef5d2013-07-26 19:29:39 +00005import argparse
zachr@google.com48b88912013-07-25 19:49:17 +00006import BaseHTTPServer
zachr@google.com785ef5d2013-07-26 19:29:39 +00007import json
zachr@google.com48b88912013-07-25 19:49:17 +00008import os
9import os.path
zachr@google.com785ef5d2013-07-26 19:29:39 +000010import re
zachr@google.com6f8e2c52013-08-07 15:43:04 +000011import subprocess
zachr@google.com785ef5d2013-07-26 19:29:39 +000012import sys
13import tempfile
14import urllib2
15
16# Grab the script path because that is where all the static assets are
17SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
18
19# Find the tools directory for python imports
20TOOLS_DIR = os.path.dirname(SCRIPT_DIR)
21
22# Find the root of the skia trunk for finding skpdiff binary
23SKIA_ROOT_DIR = os.path.dirname(TOOLS_DIR)
24
25# Find the default location of gm expectations
26DEFAULT_GM_EXPECTATIONS_DIR = os.path.join(SKIA_ROOT_DIR, 'expectations', 'gm')
27
28# Imports from within Skia
29if TOOLS_DIR not in sys.path:
30 sys.path.append(TOOLS_DIR)
31GM_DIR = os.path.join(SKIA_ROOT_DIR, 'gm')
32if GM_DIR not in sys.path:
33 sys.path.append(GM_DIR)
34import gm_json
35import jsondiff
zachr@google.com48b88912013-07-25 19:49:17 +000036
37# A simple dictionary of file name extensions to MIME types. The empty string
38# entry is used as the default when no extension was given or if the extension
39# has no entry in this dictionary.
40MIME_TYPE_MAP = {'': 'application/octet-stream',
41 'html': 'text/html',
42 'css': 'text/css',
43 'png': 'image/png',
zachr@google.com785ef5d2013-07-26 19:29:39 +000044 'js': 'application/javascript',
45 'json': 'application/json'
zachr@google.com48b88912013-07-25 19:49:17 +000046 }
47
48
zachr@google.com785ef5d2013-07-26 19:29:39 +000049IMAGE_FILENAME_RE = re.compile(gm_json.IMAGE_FILENAME_PATTERN)
50
51SKPDIFF_INVOKE_FORMAT = '{} --jsonp=false -o {} -f {} {}'
52
53
54def get_skpdiff_path(user_path=None):
55 """Find the skpdiff binary.
56
57 @param user_path If none, searches in Release and Debug out directories of
58 the skia root. If set, checks that the path is a real file and
59 returns it.
60 """
61 skpdiff_path = None
62 possible_paths = []
63
64 # Use the user given path, or try out some good default paths.
65 if user_path:
66 possible_paths.append(user_path)
67 else:
68 possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out',
69 'Release', 'skpdiff'))
70 possible_paths.append(os.path.join(SKIA_ROOT_DIR, 'out',
71 'Debug', 'skpdiff'))
72 # Use the first path that actually points to the binary
73 for possible_path in possible_paths:
74 if os.path.isfile(possible_path):
75 skpdiff_path = possible_path
76 break
77
78 # If skpdiff was not found, print out diagnostic info for the user.
79 if skpdiff_path is None:
80 print('Could not find skpdiff binary. Either build it into the ' +
81 'default directory, or specify the path on the command line.')
82 print('skpdiff paths tried:')
83 for possible_path in possible_paths:
84 print(' ', possible_path)
85 return skpdiff_path
86
87
88def download_file(url, output_path):
89 """Download the file at url and place it in output_path"""
90 reader = urllib2.urlopen(url)
91 with open(output_path, 'wb') as writer:
92 writer.write(reader.read())
93
94
95def download_gm_image(image_name, image_path, hash_val):
96 """Download the gm result into the given path.
97
98 @param image_name The GM file name, for example imageblur_gpu.png.
99 @param image_path Path to place the image.
100 @param hash_val The hash value of the image.
101 """
zachr@google.com74c5ab12013-08-07 18:06:39 +0000102 if hash_val is None:
103 return
zachr@google.com785ef5d2013-07-26 19:29:39 +0000104
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000105 # Separate the test name from a image name
zachr@google.com785ef5d2013-07-26 19:29:39 +0000106 image_match = IMAGE_FILENAME_RE.match(image_name)
107 test_name = image_match.group(1)
108
109 # Calculate the URL of the requested image
110 image_url = gm_json.CreateGmActualUrl(
111 test_name, gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5, hash_val)
112
113 # Download the image as requested
114 download_file(image_url, image_path)
115
116
zachr@google.com785ef5d2013-07-26 19:29:39 +0000117def get_image_set_from_skpdiff(skpdiff_records):
118 """Get the set of all images references in the given records.
119
120 @param skpdiff_records An array of records, which are dictionary objects.
121 """
122 expected_set = frozenset([r['baselinePath'] for r in skpdiff_records])
123 actual_set = frozenset([r['testPath'] for r in skpdiff_records])
124 return expected_set | actual_set
125
126
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000127def set_expected_hash_in_json(expected_results_json, image_name, hash_value):
128 """Set the expected hash for the object extracted from
129 expected-results.json. Note that this only work with bitmap-64bitMD5 hash
130 types.
131
132 @param expected_results_json The Python dictionary with the results to
133 modify.
134 @param image_name The name of the image to set the hash of.
135 @param hash_value The hash to set for the image.
136 """
137 expected_results = expected_results_json[gm_json.JSONKEY_EXPECTEDRESULTS]
138
139 if image_name in expected_results:
140 expected_results[image_name][gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS][0][1] = hash_value
141 else:
142 expected_results[image_name] = {
143 gm_json.JSONKEY_EXPECTEDRESULTS_ALLOWEDDIGESTS:
144 [
145 [
146 gm_json.JSONKEY_HASHTYPE_BITMAP_64BITMD5,
147 hash_value
148 ]
149 ]
150 }
151
152
153def get_head_version(path):
154 """Get the version of the file at the given path stored inside the HEAD of
155 the git repository. It is returned as a string.
156
157 @param path The path of the file whose HEAD is returned. It is assumed the
158 path is inside a git repo rooted at SKIA_ROOT_DIR.
159 """
160
161 # git-show will not work with absolute paths. This ensures we give it a path
162 # relative to the skia root.
163 git_path = os.path.relpath(path, SKIA_ROOT_DIR)
164 git_show_proc = subprocess.Popen(['git', 'show', 'HEAD:' + git_path],
165 stdout=subprocess.PIPE)
166
167 # When invoked outside a shell, git will output the last committed version
168 # of the file directly to stdout.
169 git_version_content, _ = git_show_proc.communicate()
170 return git_version_content
171
172
173class GMInstance:
174 """Information about a GM test result on a specific device:
175 - device_name = the name of the device that rendered it
176 - image_name = the GM test name and config
177 - expected_hash = the current expected hash value
178 - actual_hash = the actual hash value
zachr@google.com74c5ab12013-08-07 18:06:39 +0000179 - is_rebaselined = True if actual_hash is what is currently in the expected
180 results file, False otherwise.
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000181 """
182 def __init__(self,
183 device_name, image_name,
zachr@google.com74c5ab12013-08-07 18:06:39 +0000184 expected_hash, actual_hash,
185 is_rebaselined):
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000186 self.device_name = device_name
187 self.image_name = image_name
188 self.expected_hash = expected_hash
189 self.actual_hash = actual_hash
zachr@google.com74c5ab12013-08-07 18:06:39 +0000190 self.is_rebaselined = is_rebaselined
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000191
192
193class ExpectationsManager:
194 def __init__(self, expectations_dir, expected_name, updated_name,
195 skpdiff_path):
196 """
197 @param expectations_dir The directory to traverse for results files.
198 This should resemble expectations/gm in the Skia trunk.
199 @param expected_name The name of the expected result files. These
200 are in the format of expected-results.json.
201 @param updated_name The name of the updated expected result files.
202 Normally this matches --expectations-filename-output for the
203 rebaseline.py tool.
204 @param skpdiff_path The path used to execute the skpdiff command.
205 """
206 self._expectations_dir = expectations_dir
207 self._expected_name = expected_name
208 self._updated_name = updated_name
209 self._skpdiff_path = skpdiff_path
210 self._generate_gm_comparison()
211
212 def _generate_gm_comparison(self):
213 """Generate all the data needed to compare GMs:
214 - determine which GMs changed
215 - download the changed images
216 - compare them with skpdiff
217 """
218
219 # Get the expectations and compare them with actual hashes
220 self._get_expectations()
221
222
223 # Create a temporary file tree that makes sense for skpdiff to operate
224 # on.
225 image_output_dir = tempfile.mkdtemp('skpdiff')
226 expected_image_dir = os.path.join(image_output_dir, 'expected')
227 actual_image_dir = os.path.join(image_output_dir, 'actual')
228 os.mkdir(expected_image_dir)
229 os.mkdir(actual_image_dir)
230
231 # Download expected and actual images that differed into the temporary
232 # file tree.
233 self._download_expectation_images(expected_image_dir, actual_image_dir)
234
235 # Invoke skpdiff with our downloaded images and place its results in the
236 # temporary directory.
zachr@google.com74c5ab12013-08-07 18:06:39 +0000237 self._skpdiff_output_path = os.path.join(image_output_dir,
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000238 'skpdiff_output.json')
239 skpdiff_cmd = SKPDIFF_INVOKE_FORMAT.format(self._skpdiff_path,
zachr@google.com74c5ab12013-08-07 18:06:39 +0000240 self._skpdiff_output_path,
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000241 expected_image_dir,
242 actual_image_dir)
243 os.system(skpdiff_cmd)
zachr@google.com74c5ab12013-08-07 18:06:39 +0000244 self._load_skpdiff_output()
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000245
246
247 def _get_expectations(self):
248 """Fills self._expectations with GMInstance objects for each test whose
249 expectation is different between the following two files:
250 - the local filesystem's updated results file
251 - git's head version of the expected results file
252 """
253 differ = jsondiff.GMDiffer()
254 self._expectations = []
255 for root, dirs, files in os.walk(self._expectations_dir):
256 for expectation_file in files:
257 # There are many files in the expectations directory. We only
258 # care about expected results.
259 if expectation_file != self._expected_name:
260 continue
261
262 # Get the name of the results file, and be sure there is an
263 # updated result to compare against. If there is not, there is
264 # no point in diffing this device.
265 expected_file_path = os.path.join(root, self._expected_name)
266 updated_file_path = os.path.join(root, self._updated_name)
267 if not os.path.isfile(updated_file_path):
268 continue
269
270 # Always get the expected results from git because we may have
271 # changed them in a previous instance of the server.
272 expected_contents = get_head_version(expected_file_path)
273 updated_contents = None
274 with open(updated_file_path, 'rb') as updated_file:
275 updated_contents = updated_file.read()
276
zachr@google.com74c5ab12013-08-07 18:06:39 +0000277 # Read the expected results on disk to determine what we've
278 # already rebaselined.
279 commited_contents = None
280 with open(expected_file_path, 'rb') as expected_file:
281 commited_contents = expected_file.read()
282
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000283 # Find all expectations that did not match.
284 expected_diff = differ.GenerateDiffDictFromStrings(
285 expected_contents,
286 updated_contents)
287
zachr@google.com74c5ab12013-08-07 18:06:39 +0000288 # Generate a set of images that have already been rebaselined
289 # onto disk.
290 rebaselined_diff = differ.GenerateDiffDictFromStrings(
291 expected_contents,
292 commited_contents)
293
294 rebaselined_set = set(rebaselined_diff.keys())
295
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000296 # The name of the device corresponds to the name of the folder
297 # we are in.
298 device_name = os.path.basename(root)
299
300 # Store old and new versions of the expectation for each GM
301 for image_name, hashes in expected_diff.iteritems():
302 self._expectations.append(
303 GMInstance(device_name, image_name,
zachr@google.com74c5ab12013-08-07 18:06:39 +0000304 hashes['old'], hashes['new'],
305 image_name in rebaselined_set))
306
307 def _load_skpdiff_output(self):
308 """Loads the results of skpdiff and annotates them with whether they
309 have already been rebaselined or not. The resulting data is store in
310 self.skpdiff_records."""
311 self.skpdiff_records = None
312 with open(self._skpdiff_output_path, 'rb') as skpdiff_output_file:
313 self.skpdiff_records = json.load(skpdiff_output_file)['records']
314 for record in self.skpdiff_records:
315 record['isRebaselined'] = self.image_map[record['baselinePath']][1].is_rebaselined
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000316
317
318 def _download_expectation_images(self, expected_image_dir, actual_image_dir):
319 """Download the expected and actual images for the _expectations array.
320
321 @param expected_image_dir The directory to download expected images
322 into.
323 @param actual_image_dir The directory to download actual images into.
324 """
325 image_map = {}
326
327 # Look through expectations and download their images.
328 for expectation in self._expectations:
329 # Build appropriate paths to download the images into.
330 expected_image_path = os.path.join(expected_image_dir,
331 expectation.device_name + '-' +
332 expectation.image_name)
333
334 actual_image_path = os.path.join(actual_image_dir,
335 expectation.device_name + '-' +
336 expectation.image_name)
337
338 print('Downloading %s for device %s' % (
339 expectation.image_name, expectation.device_name))
340
341 # Download images
342 download_gm_image(expectation.image_name,
343 expected_image_path,
344 expectation.expected_hash)
345
346 download_gm_image(expectation.image_name,
347 actual_image_path,
348 expectation.actual_hash)
349
350 # Annotate the expectations with where the images were downloaded
351 # to.
352 expectation.expected_image_path = expected_image_path
353 expectation.actual_image_path = actual_image_path
354
355 # Map the image paths back to the expectations.
356 image_map[expected_image_path] = (False, expectation)
357 image_map[actual_image_path] = (True, expectation)
358
359 self.image_map = image_map
360
361 def _set_expected_hash(self, device_name, image_name, hash_value):
362 """Set the expected hash for the image of the given device. This always
363 writes directly to the expected results file of the given device
364
365 @param device_name The name of the device to write the hash to.
366 @param image_name The name of the image whose hash to set.
367 @param hash_value The value of the hash to set.
368 """
369
370 # Retrieve the expected results file as it is in the working tree
371 json_path = os.path.join(self._expectations_dir, device_name,
372 self._expected_name)
373 expectations = gm_json.LoadFromFile(json_path)
374
375 # Set the specified hash.
376 set_expected_hash_in_json(expectations, image_name, hash_value)
377
378 # Write it out to disk using gm_json to keep the formatting consistent.
379 gm_json.WriteToFile(expectations, json_path)
380
zachr@google.com74c5ab12013-08-07 18:06:39 +0000381 def commit_rebaselines(self, rebaselines):
382 """Sets the expected results file to use the hashes of the images in
383 the rebaselines list. If a expected result image is not in rebaselines
384 at all, the old hash will be used.
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000385
zachr@google.com74c5ab12013-08-07 18:06:39 +0000386 @param rebaselines A list of image paths to use the hash of.
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000387 """
zachr@google.com74c5ab12013-08-07 18:06:39 +0000388 # Reset all expectations to their old hashes because some of them may
389 # have been set to the new hash by a previous call to this function.
390 for expectation in self._expectations:
391 expectation.is_rebaselined = False
392 self._set_expected_hash(expectation.device_name,
393 expectation.image_name,
394 expectation.expected_hash)
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000395
zachr@google.com74c5ab12013-08-07 18:06:39 +0000396 # Take all the images to rebaseline
397 for image_path in rebaselines:
398 # Get the metadata about the image at the path.
399 is_actual, expectation = self.image_map[image_path]
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000400
zachr@google.com74c5ab12013-08-07 18:06:39 +0000401 expectation.is_rebaselined = is_actual
402 expectation_hash = expectation.actual_hash if is_actual else\
403 expectation.expected_hash
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000404
zachr@google.com74c5ab12013-08-07 18:06:39 +0000405 # Write out that image's hash directly to the expected results file.
406 self._set_expected_hash(expectation.device_name,
407 expectation.image_name,
408 expectation_hash)
409
410 self._load_skpdiff_output()
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000411
412
zachr@google.com48b88912013-07-25 19:49:17 +0000413class SkPDiffHandler(BaseHTTPServer.BaseHTTPRequestHandler):
414 def send_file(self, file_path):
415 # Grab the extension if there is one
416 extension = os.path.splitext(file_path)[1]
417 if len(extension) >= 1:
418 extension = extension[1:]
419
420 # Determine the MIME type of the file from its extension
421 mime_type = MIME_TYPE_MAP.get(extension, MIME_TYPE_MAP[''])
422
423 # Open the file and send it over HTTP
zachr@google.com785ef5d2013-07-26 19:29:39 +0000424 if os.path.isfile(file_path):
425 with open(file_path, 'rb') as sending_file:
426 self.send_response(200)
427 self.send_header('Content-type', mime_type)
428 self.end_headers()
429 self.wfile.write(sending_file.read())
430 else:
431 self.send_error(404)
zachr@google.com48b88912013-07-25 19:49:17 +0000432
433 def serve_if_in_dir(self, dir_path, file_path):
434 # Determine if the file exists relative to the given dir_path AND exists
435 # under the dir_path. This is to prevent accidentally serving files
436 # outside the directory intended using symlinks, or '../'.
437 real_path = os.path.normpath(os.path.join(dir_path, file_path))
zachr@google.com48b88912013-07-25 19:49:17 +0000438 if os.path.commonprefix([real_path, dir_path]) == dir_path:
439 if os.path.isfile(real_path):
440 self.send_file(real_path)
441 return True
442 return False
443
444 def do_GET(self):
zachr@google.com48b88912013-07-25 19:49:17 +0000445 # Simple rewrite rule of the root path to 'viewer.html'
446 if self.path == '' or self.path == '/':
447 self.path = '/viewer.html'
448
449 # The [1:] chops off the leading '/'
450 file_path = self.path[1:]
451
zachr@google.com785ef5d2013-07-26 19:29:39 +0000452 # Handle skpdiff_output.json manually because it is was processed by the
453 # server when it was started and does not exist as a file.
454 if file_path == 'skpdiff_output.json':
455 self.send_response(200)
456 self.send_header('Content-type', MIME_TYPE_MAP['json'])
457 self.end_headers()
zachr@google.com74c5ab12013-08-07 18:06:39 +0000458
459 # Add JSONP padding to the JSON because the web page expects it. It
460 # expects it because it was designed to run with or without a web
461 # server. Without a web server, the only way to load JSON is with
462 # JSONP.
463 skpdiff_records = self.server.expectations_manager.skpdiff_records
464 self.wfile.write('var SkPDiffRecords = ')
465 json.dump({'records': skpdiff_records}, self.wfile)
466 self.wfile.write(';')
zachr@google.com48b88912013-07-25 19:49:17 +0000467 return
468
zachr@google.com785ef5d2013-07-26 19:29:39 +0000469 # Attempt to send static asset files first.
470 if self.serve_if_in_dir(SCRIPT_DIR, file_path):
471 return
472
473 # WARNING: Serving any file the user wants is incredibly insecure. Its
474 # redeeming quality is that we only serve gm files on a white list.
475 if self.path in self.server.image_set:
476 self.send_file(self.path)
zachr@google.com48b88912013-07-25 19:49:17 +0000477 return
478
479 # If no file to send was found, just give the standard 404
480 self.send_error(404)
481
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000482 def do_POST(self):
zachr@google.com74c5ab12013-08-07 18:06:39 +0000483 if self.path == '/commit_rebaselines':
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000484 content_length = int(self.headers['Content-length'])
485 request_data = json.loads(self.rfile.read(content_length))
zachr@google.com74c5ab12013-08-07 18:06:39 +0000486 rebaselines = request_data['rebaselines']
487 self.server.expectations_manager.commit_rebaselines(rebaselines)
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000488 self.send_response(200)
489 self.send_header('Content-type', 'application/json')
490 self.end_headers()
491 self.wfile.write('{"success":true}')
492 return
zachr@google.com48b88912013-07-25 19:49:17 +0000493
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000494 # If the we have no handler for this path, give em' the 404
495 self.send_error(404)
496
497
498def run_server(expectations_manager, port=8080):
zachr@google.com785ef5d2013-07-26 19:29:39 +0000499 # It's important to parse the results file so that we can make a set of
500 # images that the web page might request.
zachr@google.com74c5ab12013-08-07 18:06:39 +0000501 skpdiff_records = expectations_manager.skpdiff_records
zachr@google.com785ef5d2013-07-26 19:29:39 +0000502 image_set = get_image_set_from_skpdiff(skpdiff_records)
503
zachr@google.com48b88912013-07-25 19:49:17 +0000504 # Do not bind to interfaces other than localhost because the server will
505 # attempt to serve files relative to the root directory as a last resort
506 # before 404ing. This means all of your files can be accessed from this
507 # server, so DO NOT let this server listen to anything but localhost.
zachr@google.com785ef5d2013-07-26 19:29:39 +0000508 server_address = ('127.0.0.1', port)
zachr@google.com48b88912013-07-25 19:49:17 +0000509 http_server = BaseHTTPServer.HTTPServer(server_address, SkPDiffHandler)
zachr@google.com785ef5d2013-07-26 19:29:39 +0000510 http_server.image_set = image_set
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000511 http_server.expectations_manager = expectations_manager
512 print('Navigate thine browser to: http://{}:{}/'.format(*server_address))
zachr@google.com48b88912013-07-25 19:49:17 +0000513 http_server.serve_forever()
514
zachr@google.com785ef5d2013-07-26 19:29:39 +0000515
516def main():
517 parser = argparse.ArgumentParser()
518 parser.add_argument('--port', '-p', metavar='PORT',
519 type=int,
520 default=8080,
521 help='port to bind the server to; ' +
522 'defaults to %(default)s',
523 )
524
525 parser.add_argument('--expectations-dir', metavar='EXPECTATIONS_DIR',
526 default=DEFAULT_GM_EXPECTATIONS_DIR,
527 help='path to the gm expectations; ' +
528 'defaults to %(default)s'
529 )
530
531 parser.add_argument('--expected',
532 metavar='EXPECTATIONS_FILE_NAME',
533 default='expected-results.json',
534 help='the file name of the expectations JSON; ' +
535 'defaults to %(default)s'
536 )
537
538 parser.add_argument('--updated',
539 metavar='UPDATED_FILE_NAME',
540 default='updated-results.json',
541 help='the file name of the updated expectations JSON;' +
542 ' defaults to %(default)s'
543 )
544
545 parser.add_argument('--skpdiff-path', metavar='SKPDIFF_PATH',
546 default=None,
547 help='the path to the skpdiff binary to use; ' +
548 'defaults to out/Release/skpdiff or out/Default/skpdiff'
549 )
550
551 args = vars(parser.parse_args()) # Convert args into a python dict
552
553 # Make sure we have access to an skpdiff binary
554 skpdiff_path = get_skpdiff_path(args['skpdiff_path'])
555 if skpdiff_path is None:
556 sys.exit(1)
557
zachr@google.com785ef5d2013-07-26 19:29:39 +0000558 # Print out the paths of things for easier debugging
559 print('script dir :', SCRIPT_DIR)
560 print('tools dir :', TOOLS_DIR)
561 print('root dir :', SKIA_ROOT_DIR)
562 print('expectations dir :', args['expectations_dir'])
563 print('skpdiff path :', skpdiff_path)
zachr@google.com785ef5d2013-07-26 19:29:39 +0000564
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000565 expectations_manager = ExpectationsManager(args['expectations_dir'],
566 args['expected'],
567 args['updated'],
568 skpdiff_path)
zachr@google.com785ef5d2013-07-26 19:29:39 +0000569
zachr@google.com6f8e2c52013-08-07 15:43:04 +0000570 run_server(expectations_manager, port=args['port'])
zachr@google.com785ef5d2013-07-26 19:29:39 +0000571
zachr@google.com48b88912013-07-25 19:49:17 +0000572if __name__ == '__main__':
573 main()