1#!/usr/bin/env python 2# Copyright 2013 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6# Run build_server so that files needed by tests are copied to the local 7# third_party directory. 8import build_server 9build_server.main() 10 11import json 12import optparse 13import os 14import posixpath 15import sys 16import time 17import unittest 18 19from appengine_wrappers import SetTaskRunnerForTest 20from branch_utility import BranchUtility 21from chroot_file_system import ChrootFileSystem 22from extensions_paths import ( 23 CONTENT_PROVIDERS, CHROME_EXTENSIONS, PUBLIC_TEMPLATES) 24from fake_fetchers import ConfigureFakeFetchers 25from special_paths import SITE_VERIFICATION_FILE 26from handler import Handler 27from link_error_detector import LinkErrorDetector, StringifyBrokenLinks 28from local_file_system import LocalFileSystem 29from local_renderer import LocalRenderer 30from path_util import AssertIsValid 31from servlet import Request 32from third_party.json_schema_compiler import json_parse 33from test_util import ( 34 ChromiumPath, DisableLogging, EnableLogging, ReadFile, Server2Path) 35 36 37# Arguments set up if __main__ specifies them. 38_EXPLICIT_TEST_FILES = None 39_REBASE = False 40_VERBOSE = False 41 42 43def _ToPosixPath(os_path): 44 return os_path.replace(os.sep, '/') 45 46 47def _FilterHidden(paths): 48 '''Returns a list of the non-hidden paths from |paths|. 49 ''' 50 # Hidden files start with a '.' but paths like './foo' and '../foo' are not 51 # hidden. 52 return [path for path in paths if (not path.startswith('.')) or 53 path.startswith('./') or 54 path.startswith('../')] 55 56 57def _GetPublicFiles(): 58 '''Gets all public file paths mapped to their contents. 59 ''' 60 def walk(path, prefix=''): 61 path = ChromiumPath(path) 62 public_files = {} 63 for root, dirs, files in os.walk(path, topdown=True): 64 relative_root = root[len(path):].lstrip(os.path.sep) 65 dirs[:] = _FilterHidden(dirs) 66 for filename in _FilterHidden(files): 67 with open(os.path.join(root, filename), 'r') as f: 68 request_path = posixpath.join(prefix, relative_root, filename) 69 public_files[request_path] = f.read() 70 return public_files 71 72 # Public file locations are defined in content_providers.json, sort of. Epic 73 # hack to pull them out; list all the files from the directories that 74 # Chromium content providers ask for. 75 public_files = {} 76 content_providers = json_parse.Parse(ReadFile(CONTENT_PROVIDERS)) 77 for content_provider in content_providers.itervalues(): 78 if 'chromium' in content_provider: 79 public_files.update(walk(content_provider['chromium']['dir'], 80 prefix=content_provider['serveFrom'])) 81 return public_files 82 83 84class IntegrationTest(unittest.TestCase): 85 def setUp(self): 86 ConfigureFakeFetchers() 87 88 @EnableLogging('info') 89 def testCronAndPublicFiles(self): 90 '''Runs cron then requests every public file. Cron needs to be run first 91 because the public file requests are offline. 92 ''' 93 if _EXPLICIT_TEST_FILES is not None: 94 return 95 96 97 def task_runner(url, commit=None): 98 arguments = { 'commit': commit } if commit else {} 99 Handler(Request.ForTest(url, arguments=arguments)).Get() 100 101 SetTaskRunnerForTest(task_runner) 102 103 print('Running cron...') 104 start_time = time.time() 105 try: 106 response = Handler(Request.ForTest('/_cron')).Get() 107 if response: 108 self.assertEqual(200, response.status) 109 self.assertEqual('Success', response.content.ToString()) 110 else: 111 self.fail('No response for _cron') 112 finally: 113 print('Took %s seconds' % (time.time() - start_time)) 114 115 # TODO(kalman): Re-enable this, but it takes about an hour at the moment, 116 # presumably because every page now has a lot of links on it from the 117 # topnav. 118 119 #print("Checking for broken links...") 120 #start_time = time.time() 121 #link_error_detector = LinkErrorDetector( 122 # # TODO(kalman): Use of ChrootFileSystem here indicates a hack. Fix. 123 # ChrootFileSystem(LocalFileSystem.Create(), CHROME_EXTENSIONS), 124 # lambda path: Handler(Request.ForTest(path)).Get(), 125 # 'templates/public', 126 # ('extensions/index.html', 'apps/about_apps.html')) 127 128 #broken_links = link_error_detector.GetBrokenLinks() 129 #if broken_links: 130 # print('Found %d broken links.' % ( 131 # len(broken_links))) 132 # if _VERBOSE: 133 # print(StringifyBrokenLinks(broken_links)) 134 135 #broken_links_set = set(broken_links) 136 137 #known_broken_links_path = os.path.join( 138 # Server2Path('known_broken_links.json')) 139 #try: 140 # with open(known_broken_links_path, 'r') as f: 141 # # The JSON file converts tuples and sets into lists, and for this 142 # # set union/difference logic they need to be converted back. 143 # known_broken_links = set(tuple(item) for item in json.load(f)) 144 #except IOError: 145 # known_broken_links = set() 146 147 #newly_broken_links = broken_links_set - known_broken_links 148 #fixed_links = known_broken_links - broken_links_set 149 150 #print('Took %s seconds.' % (time.time() - start_time)) 151 152 #print('Searching for orphaned pages...') 153 #start_time = time.time() 154 #orphaned_pages = link_error_detector.GetOrphanedPages() 155 #if orphaned_pages: 156 # # TODO(jshumway): Test should fail when orphaned pages are detected. 157 # print('Found %d orphaned pages:' % len(orphaned_pages)) 158 # for page in orphaned_pages: 159 # print(page) 160 #print('Took %s seconds.' % (time.time() - start_time)) 161 162 public_files = _GetPublicFiles() 163 164 print('Rendering %s public files...' % len(public_files.keys())) 165 start_time = time.time() 166 try: 167 for path, content in public_files.iteritems(): 168 AssertIsValid(path) 169 if path.endswith('redirects.json'): 170 continue 171 172 # The non-example html and md files are served without their file 173 # extensions. 174 path_without_ext, ext = posixpath.splitext(path) 175 if (ext in ('.html', '.md') and 176 '/examples/' not in path and 177 path != SITE_VERIFICATION_FILE): 178 path = path_without_ext 179 180 def check_result(response): 181 self.assertEqual(200, response.status, 182 'Got %s when rendering %s' % (response.status, path)) 183 184 # This is reaaaaally rough since usually these will be tiny templates 185 # that render large files. At least it'll catch zero-length responses. 186 self.assertTrue(len(response.content) >= len(content), 187 'Rendered content length was %s vs template content length %s ' 188 'when rendering %s' % (len(response.content), len(content), path)) 189 190 check_result(Handler(Request.ForTest(path)).Get()) 191 192 if path.startswith(('apps/', 'extensions/')): 193 # Make sure that adding the .html will temporarily redirect to 194 # the path without the .html for APIs and articles. 195 if '/examples/' not in path: 196 redirect_response = Handler(Request.ForTest(path + '.html')).Get() 197 self.assertEqual( 198 ('/' + path, False), redirect_response.GetRedirect(), 199 '%s.html did not (temporarily) redirect to %s (status %s)' % 200 (path, path, redirect_response.status)) 201 202 # Make sure including a channel will permanently redirect to the same 203 # path without a channel. 204 for channel in BranchUtility.GetAllChannelNames(): 205 redirect_response = Handler( 206 Request.ForTest(posixpath.join(channel, path))).Get() 207 self.assertEqual( 208 ('/' + path, True), 209 redirect_response.GetRedirect(), 210 '%s/%s did not (permanently) redirect to %s (status %s)' % 211 (channel, path, path, redirect_response.status)) 212 213 # Samples are internationalized, test some locales. 214 if path.endswith('/samples'): 215 for lang in ('en-US', 'es', 'ar'): 216 check_result(Handler(Request.ForTest( 217 path, 218 headers={'Accept-Language': '%s;q=0.8' % lang})).Get()) 219 finally: 220 print('Took %s seconds' % (time.time() - start_time)) 221 222 #if _REBASE: 223 # print('Rebasing broken links with %s newly broken and %s fixed links.' % 224 # (len(newly_broken_links), len(fixed_links))) 225 # with open(known_broken_links_path, 'w') as f: 226 # json.dump(broken_links, f, 227 # indent=2, separators=(',', ': '), sort_keys=True) 228 #else: 229 # if fixed_links or newly_broken_links: 230 # print('**********************************************\n' 231 # 'CHANGE DETECTED IN BROKEN LINKS WITHOUT REBASE\n' 232 # '**********************************************') 233 # print('Found %s broken links, and some have changed. ' 234 # 'If this is acceptable or expected then run %s with the --rebase ' 235 # 'option.' % (len(broken_links), os.path.split(__file__)[-1])) 236 # elif broken_links: 237 # print('%s existing broken links' % len(broken_links)) 238 # if fixed_links: 239 # print('%s broken links have been fixed:' % len(fixed_links)) 240 # print(StringifyBrokenLinks(fixed_links)) 241 # if newly_broken_links: 242 # print('There are %s new broken links:' % len(newly_broken_links)) 243 # print(StringifyBrokenLinks(newly_broken_links)) 244 # self.fail('See logging for details.') 245 246 # TODO(kalman): Move this test elsewhere, it's not an integration test. 247 # Perhaps like "presubmit_tests" or something. 248 def testExplicitFiles(self): 249 '''Tests just the files in _EXPLICIT_TEST_FILES. 250 ''' 251 if _EXPLICIT_TEST_FILES is None: 252 return 253 for filename in _EXPLICIT_TEST_FILES: 254 print('Rendering %s...' % filename) 255 start_time = time.time() 256 try: 257 response = LocalRenderer.Render(_ToPosixPath(filename)) 258 self.assertEqual(200, response.status) 259 self.assertTrue(response.content != '') 260 finally: 261 print('Took %s seconds' % (time.time() - start_time)) 262 263 # TODO(jshumway): Check page for broken links (currently prohibited by the 264 # time it takes to render the pages). 265 266 @DisableLogging('warning') 267 def testFileNotFound(self): 268 response = LocalRenderer.Render('/extensions/notfound') 269 self.assertEqual(404, response.status) 270 271 def testSiteVerificationFile(self): 272 response = LocalRenderer.Render('/' + SITE_VERIFICATION_FILE) 273 self.assertEqual(200, response.status) 274 275if __name__ == '__main__': 276 parser = optparse.OptionParser() 277 parser.add_option('-a', '--all', action='store_true', default=False, 278 help='Render all pages, not just the one specified') 279 parser.add_option('-r', '--rebase', action='store_true', default=False, 280 help='Rewrites the known_broken_links.json file with ' 281 'the current set of broken links') 282 parser.add_option('-v', '--verbose', action='store_true', default=False, 283 help='Show verbose output like currently broken links') 284 (opts, args) = parser.parse_args() 285 if not opts.all: 286 _EXPLICIT_TEST_FILES = args 287 _REBASE = opts.rebase 288 _VERBOSE = opts.verbose 289 # Kill sys.argv because we have our own flags. 290 sys.argv = [sys.argv[0]] 291 unittest.main() 292