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