Ensure low-memory renderers retry failed loads correctly.
[chromium-blink-merge.git] / ppapi / native_client / chrome_main.scons
blobb6154c6e59051518223f2b47837daea927ae5f93
1 #! -*- python -*-
2 # Copyright (c) 2012 The Native Client 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.
6 import json
7 import os
8 import shutil
9 import sys
11 sys.path.append(Dir('#/tools').abspath)
12 import command_tester
13 import test_lib
15 Import(['pre_base_env'])
17 # Underlay things migrating to ppapi repo.
18 Dir('#/..').addRepository(Dir('#/../ppapi'))
20 # Append a list of files to another, filtering out the files that already exist.
21 # Filtering helps migrate declarations between repos by preventing redundant
22 # declarations from causing an error.
23 def ExtendFileList(existing, additional):
24   # Avoid quadratic behavior by using a set.
25   combined = set()
26   for file_name in existing + additional:
27     if file_name in combined:
28       print 'WARNING: two references to file %s in the build.' % file_name
29     combined.add(file_name)
30   return sorted(combined)
33 ppapi_scons_files = {}
34 ppapi_scons_files['trusted_scons_files'] = []
35 ppapi_scons_files['untrusted_irt_scons_files'] = []
37 ppapi_scons_files['nonvariant_test_scons_files'] = [
38     'tests/breakpad_crash_test/nacl.scons',
39     'tests/nacl_browser/browser_dynamic_library/nacl.scons',
40     'tests/ppapi_test_lib/nacl.scons',
43 ppapi_scons_files['irt_variant_test_scons_files'] = [
44     # 'inbrowser_test_runner' must be in the irt_variant list
45     # otherwise it will run no tests.
46     'tests/nacl_browser/inbrowser_test_runner/nacl.scons',
49 ppapi_scons_files['untrusted_scons_files'] = [
50     'src/shared/ppapi/nacl.scons',
51     'src/untrusted/irt_stub/nacl.scons',
55 EXTRA_ENV = [
56     'XAUTHORITY', 'HOME', 'DISPLAY', 'SSH_TTY', 'KRB5CCNAME',
57     'CHROME_DEVEL_SANDBOX' ]
59 def SetupBrowserEnv(env):
60   for var_name in EXTRA_ENV:
61     if var_name in os.environ:
62       env['ENV'][var_name] = os.environ[var_name]
64 pre_base_env.AddMethod(SetupBrowserEnv)
67 def GetHeadlessPrefix(env):
68   if env.Bit('browser_headless') and env.Bit('host_linux'):
69     return ['xvfb-run', '--auto-servernum']
70   else:
71     # Mac and Windows do not seem to have an equivalent.
72     return []
74 pre_base_env.AddMethod(GetHeadlessPrefix)
77 # A fake file to depend on if a path to Chrome is not specified.
78 no_browser = pre_base_env.File('chrome_browser_path_not_specified')
81 # SCons attempts to run a test that depends on "no_browser", detect this at
82 # runtime and cause a build error.
83 def NoBrowserError(target, source, env):
84   print target, source, env
85   print ("***\nYou need to specificy chrome_browser_path=... on the " +
86          "command line to run these tests.\n***\n")
87   return 1
89 pre_base_env.Append(BUILDERS = {
90     'NoBrowserError': Builder(action=NoBrowserError)
93 pre_base_env.NoBrowserError([no_browser], [])
96 def ChromeBinary(env):
97   if 'chrome_browser_path' in ARGUMENTS:
98     return env.File(env.SConstructAbsPath(ARGUMENTS['chrome_browser_path']))
99   else:
100     return no_browser
102 pre_base_env.AddMethod(ChromeBinary)
105 # runnable-ld.so log has following format:
106 # lib_name => path_to_lib (0x....address)
107 def ParseLibInfoInRunnableLdLog(line):
108   pos = line.find(' => ')
109   if pos < 0:
110     return None
111   lib_name = line[:pos].strip()
112   lib_path = line[pos+4:]
113   pos1 = lib_path.rfind(' (')
114   if pos1 < 0:
115     return None
116   lib_path = lib_path[:pos1]
117   return lib_name, lib_path
120 # Expected name of the temporary .libs file which stores glibc library
121 # dependencies in "lib_name => lib_info" format
122 # (see ParseLibInfoInRunnableLdLog)
123 def GlibcManifestLibsListFilename(manifest_base_name):
124   return '${STAGING_DIR}/%s.libs' % manifest_base_name
127 # Copy libs and manifest to the target directory.
128 # source[0] is a manifest file
129 # source[1] is a .libs file with a list of libs generated by runnable-ld.so
130 def CopyLibsForExtensionCommand(target, source, env):
131   source_manifest = str(source[0])
132   target_manifest = str(target[0])
133   shutil.copyfile(source_manifest, target_manifest)
134   target_dir = os.path.dirname(target_manifest)
135   libs_file = open(str(source[1]), 'r')
136   for line in libs_file.readlines():
137     lib_info = ParseLibInfoInRunnableLdLog(line)
138     if lib_info:
139       lib_name, lib_path = lib_info
140       if lib_path == 'NaClMain':
141         # This is a fake file name, which we cannot copy.
142         continue
143       shutil.copyfile(lib_path, os.path.join(target_dir, lib_name))
144   shutil.copyfile(env.subst('${NACL_SDK_LIB}/runnable-ld.so'),
145                   os.path.join(target_dir, 'runnable-ld.so'))
146   libs_file.close()
149 # Extensions are loaded from directory on disk and so all dynamic libraries
150 # they use must be copied to extension directory. The option --extra_serving_dir
151 # does not help us in this case.
152 def CopyLibsForExtension(env, target_dir, manifest):
153   if not env.Bit('nacl_glibc'):
154     return env.Install(target_dir, manifest)
155   manifest_base_name = os.path.basename(str(env.subst(manifest)))
156   lib_list_node = env.File(GlibcManifestLibsListFilename(manifest_base_name))
157   nmf_node = env.Command(
158       target_dir + '/' + manifest_base_name,
159       [manifest, lib_list_node],
160       CopyLibsForExtensionCommand)
161   return nmf_node
163 pre_base_env.AddMethod(CopyLibsForExtension)
167 def WhitelistLibsForExtensionCommand(target, source, env):
168   # Load existing extension manifest.
169   src_file = open(source[0].abspath, 'r')
170   src_json = json.load(src_file)
171   src_file.close()
173   # Load existing 'web_accessible_resources' key.
174   if 'web_accessible_resources' not in src_json:
175     src_json['web_accessible_resources'] = []
176   web_accessible = src_json['web_accessible_resources']
178   # Load list of libraries, and add libraries to web_accessible list.
179   libs_file = open(source[1].abspath, 'r')
180   for line in libs_file.readlines():
181     lib_info = ParseLibInfoInRunnableLdLog(line)
182     if lib_info:
183       web_accessible.append(lib_info[0])
184   # Also add the dynamic loader, which won't be in the libs_file.
185   web_accessible.append('runnable-ld.so')
186   libs_file.close()
188   # Write out the appended-to extension manifest.
189   target_file = open(target[0].abspath, 'w')
190   json.dump(src_json, target_file, sort_keys=True, indent=2)
191   target_file.close()
194 # Whitelist glibc shared libraries (if necessary), so that they are
195 # 'web_accessible_resources'.  This allows the libraries hosted at the origin
196 # chrome-extension://[PACKAGE ID]/
197 # to be made available to webpages that use this NaCl extension,
198 # which are in a different origin.
199 # See: http://code.google.com/chrome/extensions/manifest.html
200 def WhitelistLibsForExtension(env, target_dir, nmf, extension_manifest):
201   if env.Bit('nacl_static_link'):
202     # For static linking, assume the nexe and nmf files are already
203     # whitelisted, so there is no need to add entries to the extension_manifest.
204     return env.Install(target_dir, extension_manifest)
205   nmf_base_name = os.path.basename(env.File(nmf).abspath)
206   lib_list_node = env.File(GlibcManifestLibsListFilename(nmf_base_name))
207   manifest_base_name = os.path.basename(env.File(extension_manifest).abspath)
208   extension_manifest_node = env.Command(
209       target_dir + '/' + manifest_base_name,
210       [extension_manifest, lib_list_node],
211       WhitelistLibsForExtensionCommand)
212   return extension_manifest_node
214 pre_base_env.AddMethod(WhitelistLibsForExtension)
217 # Generate manifest from newlib manifest and the list of libs generated by
218 # runnable-ld.so.
219 def GenerateManifestFunc(target, source, env):
220   # Open the original manifest and parse it.
221   source_file = open(str(source[0]), 'r')
222   obj = json.load(source_file)
223   source_file.close()
224   # Open the file with ldd-format list of NEEDED libs and parse it.
225   libs_file = open(str(source[1]), 'r')
226   lib_names = []
227   arch = env.subst('${TARGET_FULLARCH}')
228   for line in libs_file.readlines():
229     lib_info = ParseLibInfoInRunnableLdLog(line)
230     if lib_info:
231       lib_name, _ = lib_info
232       lib_names.append(lib_name)
233   libs_file.close()
234   # Inject the NEEDED libs into the manifest.
235   if 'files' not in obj:
236     obj['files'] = {}
237   for lib_name in lib_names:
238     obj['files'][lib_name] = {}
239     obj['files'][lib_name][arch] = {}
240     obj['files'][lib_name][arch]['url'] = lib_name
241   # Put what used to be specified under 'program' into 'main.nexe'.
242   obj['files']['main.nexe'] = {}
243   for k, v in obj['program'].items():
244     obj['files']['main.nexe'][k] = v.copy()
245     v['url'] = 'runnable-ld.so'
246   # Write the new manifest!
247   target_file = open(str(target[0]), 'w')
248   json.dump(obj, target_file, sort_keys=True, indent=2)
249   target_file.close()
250   return 0
253 def GenerateManifestDynamicLink(env, dest_file, lib_list_file,
254                                 manifest, exe_file):
255   # Run sel_ldr on the nexe to trace the NEEDED libraries.
256   lib_list_node = env.Command(
257       lib_list_file,
258       [env.GetSelLdr(),
259        '${NACL_SDK_LIB}/runnable-ld.so',
260        exe_file,
261        '${SCONSTRUCT_DIR}/DEPS'],
262       # We ignore the return code using '-' in order to build tests
263       # where binaries do not validate.  This is a Scons feature.
264       '-${SOURCES[0]} -a -E LD_TRACE_LOADED_OBJECTS=1 ${SOURCES[1]} '
265       '--library-path ${NACL_SDK_LIB}:${LIB_DIR} ${SOURCES[2].posix} '
266       '> ${TARGET}')
267   return env.Command(dest_file,
268                      [manifest, lib_list_node],
269                      GenerateManifestFunc)[0]
272 def GenerateSimpleManifestStaticLink(env, dest_file, exe_name):
273   def Func(target, source, env):
274     archs = ('x86-32', 'x86-64', 'arm')
275     nmf_data = {'program': dict((arch, {'url': '%s_%s.nexe' % (exe_name, arch)})
276                                 for arch in archs)}
277     fh = open(target[0].abspath, 'w')
278     json.dump(nmf_data, fh, sort_keys=True, indent=2)
279     fh.close()
280   node = env.Command(dest_file, [], Func)[0]
281   # Scons does not track the dependency of dest_file on exe_name or on
282   # the Python code above, so we should always recreate dest_file when
283   # it is used.
284   env.AlwaysBuild(node)
285   return node
288 def GenerateSimpleManifest(env, dest_file, exe_name):
289   if env.Bit('nacl_static_link'):
290     return GenerateSimpleManifestStaticLink(env, dest_file, exe_name)
291   else:
292     static_manifest = GenerateSimpleManifestStaticLink(
293         env, '%s.static' % dest_file, exe_name)
294     return GenerateManifestDynamicLink(
295         env, dest_file, '%s.tmp_lib_list' % dest_file, static_manifest,
296         '${STAGING_DIR}/%s.nexe' % env.ProgramNameForNmf(exe_name))
298 pre_base_env.AddMethod(GenerateSimpleManifest)
301 # Returns a pair (main program, is_portable), based on the program
302 # specified in manifest file.
303 def GetMainProgramFromManifest(env, manifest):
304   obj = json.loads(env.File(manifest).get_contents())
305   program_dict = obj['program']
306   return program_dict[env.subst('${TARGET_FULLARCH}')]['url']
309 # Returns scons node for generated manifest.
310 def GeneratedManifestNode(env, manifest):
311   manifest = env.subst(manifest)
312   manifest_base_name = os.path.basename(manifest)
313   main_program = GetMainProgramFromManifest(env, manifest)
314   result = env.File('${STAGING_DIR}/' + manifest_base_name)
315   # Always generate the manifest for nacl_glibc.
316   # For nacl_glibc, generating the mapping of shared libraries is non-trivial.
317   if not env.Bit('nacl_glibc'):
318     env.Install('${STAGING_DIR}', manifest)
319     return result
320   return GenerateManifestDynamicLink(
321       env, '${STAGING_DIR}/' + manifest_base_name,
322       # Note that CopyLibsForExtension() and WhitelistLibsForExtension()
323       # assume that it can find the library list file under this filename.
324       GlibcManifestLibsListFilename(manifest_base_name),
325       manifest,
326       env.File('${STAGING_DIR}/' + os.path.basename(main_program)))
327   return result
330 # Compares output_file and golden_file.
331 # If they are different, prints the difference and returns 1.
332 # Otherwise, returns 0.
333 def CheckGoldenFile(golden_file, output_file,
334                     filter_regex, filter_inverse, filter_group_only):
335   golden = open(golden_file).read()
336   actual = open(output_file).read()
337   if filter_regex is not None:
338     actual = test_lib.RegexpFilterLines(
339         filter_regex,
340         filter_inverse,
341         filter_group_only,
342         actual)
343   if command_tester.DifferentFromGolden(actual, golden, output_file):
344     return 1
345   return 0
348 # Returns action that compares output_file and golden_file.
349 # This action can be attached to the node with
350 # env.AddPostAction(target, action)
351 def GoldenFileCheckAction(env, output_file, golden_file,
352                           filter_regex=None, filter_inverse=False,
353                           filter_group_only=False):
354   def ActionFunc(target, source, env):
355     return CheckGoldenFile(env.subst(golden_file), env.subst(output_file),
356                            filter_regex, filter_inverse, filter_group_only)
358   return env.Action(ActionFunc)
361 def PPAPIBrowserTester(env,
362                        target,
363                        url,
364                        files,
365                        nmfs=None,
366                        # List of executable basenames to generate
367                        # manifest files for.
368                        nmf_names=(),
369                        map_files=(),
370                        extensions=(),
371                        mime_types=(),
372                        timeout=30,
373                        log_verbosity=2,
374                        args=[],
375                        # list of key/value pairs that are passed to the test
376                        test_args=(),
377                        # list of "--flag=value" pairs (no spaces!)
378                        browser_flags=None,
379                        # redirect streams of NaCl program to files
380                        nacl_exe_stdin=None,
381                        nacl_exe_stdout=None,
382                        nacl_exe_stderr=None,
383                        python_tester_script=None,
384                        **extra):
385   if 'TRUSTED_ENV' not in env:
386     return []
388   # Handle issues with mutating any python default arg lists.
389   if browser_flags is None:
390     browser_flags = []
392   # Lint the extra arguments that are being passed to the tester.
393   special_args = ['--ppapi_plugin', '--sel_ldr', '--irt_library', '--file',
394                   '--map_file', '--extension', '--mime_type', '--tool',
395                   '--browser_flag', '--test_arg']
396   for arg_name in special_args:
397     if arg_name in args:
398       raise Exception('%s: %r is a test argument provided by the SCons test'
399                       ' wrapper, do not specify it as an additional argument' %
400                       (target, arg_name))
402   env = env.Clone()
403   env.SetupBrowserEnv()
405   if 'scale_timeout' in ARGUMENTS:
406     timeout = timeout * int(ARGUMENTS['scale_timeout'])
408   if python_tester_script is None:
409     python_tester_script = env.File('${SCONSTRUCT_DIR}/tools/browser_tester'
410                              '/browser_tester.py')
411   command = env.GetHeadlessPrefix() + [
412       '${PYTHON}', python_tester_script,
413       '--browser_path', env.ChromeBinary(),
414       '--url', url,
415       # Fail if there is no response for X seconds.
416       '--timeout', str(timeout)]
417   for dep_file in files:
418     command.extend(['--file', dep_file])
419   for extension in extensions:
420     command.extend(['--extension', extension])
421   for dest_path, dep_file in map_files:
422     command.extend(['--map_file', dest_path, dep_file])
423   for file_ext, mime_type in mime_types:
424     command.extend(['--mime_type', file_ext, mime_type])
425   command.extend(['--serving_dir', '${NACL_SDK_LIB}'])
426   command.extend(['--serving_dir', '${LIB_DIR}'])
427   if 'browser_tester_bw' in ARGUMENTS:
428     command.extend(['-b', ARGUMENTS['browser_tester_bw']])
429   if not nmfs is None:
430     for nmf_file in nmfs:
431       generated_manifest = GeneratedManifestNode(env, nmf_file)
432       # We need to add generated manifests to the list of default targets.
433       # The manifests should be generated even if the tests are not run -
434       # the manifests may be needed for manual testing.
435       for group in env['COMPONENT_TEST_PROGRAM_GROUPS']:
436         env.Alias(group, generated_manifest)
437       # Generated manifests are served in the root of the HTTP server
438       command.extend(['--file', generated_manifest])
439   for nmf_name in nmf_names:
440     tmp_manifest = '%s.tmp/%s.nmf' % (target, nmf_name)
441     command.extend(['--map_file', '%s.nmf' % nmf_name,
442                     env.GenerateSimpleManifest(tmp_manifest, nmf_name)])
443   if 'browser_test_tool' in ARGUMENTS:
444     command.extend(['--tool', ARGUMENTS['browser_test_tool']])
446   # Suppress debugging information on the Chrome waterfall.
447   if env.Bit('disable_flaky_tests') and '--debug' in args:
448     args.remove('--debug')
450   command.extend(args)
451   for flag in browser_flags:
452     if flag.find(' ') != -1:
453       raise Exception('Spaces not allowed in browser_flags: '
454                       'use --flag=value instead')
455     command.extend(['--browser_flag', flag])
456   for key, value in test_args:
457     command.extend(['--test_arg', str(key), str(value)])
459   # Set a given file to be the nexe's stdin.
460   if nacl_exe_stdin is not None:
461     command.extend(['--nacl_exe_stdin', env.subst(nacl_exe_stdin['file'])])
463   post_actions = []
464   side_effects = []
465   # Set a given file to be the nexe's stdout or stderr.  The tester also
466   # compares this output against a golden file.
467   for stream, params in (
468       ('stdout', nacl_exe_stdout),
469       ('stderr', nacl_exe_stderr)):
470     if params is None:
471       continue
472     stream_file = env.subst(params['file'])
473     side_effects.append(stream_file)
474     command.extend(['--nacl_exe_' + stream, stream_file])
475     if 'golden' in params:
476       golden_file = env.subst(params['golden'])
477       filter_regex = params.get('filter_regex', None)
478       filter_inverse = params.get('filter_inverse', False)
479       filter_group_only = params.get('filter_group_only', False)
480       post_actions.append(
481           GoldenFileCheckAction(
482               env, stream_file, golden_file,
483               filter_regex, filter_inverse, filter_group_only))
485   if env.ShouldUseVerboseOptions(extra):
486     env.MakeVerboseExtraOptions(target, log_verbosity, extra)
487   # Heuristic for when to capture output...
488   capture_output = (extra.pop('capture_output', False)
489                     or 'process_output_single' in extra)
490   node = env.CommandTest(target,
491                          command,
492                          # Set to 'huge' so that the browser tester's timeout
493                          # takes precedence over the default of the test_suite.
494                          size='huge',
495                          capture_output=capture_output,
496                          **extra)
497   for side_effect in side_effects:
498     env.SideEffect(side_effect, node)
499   # We can't check output if the test is not run.
500   if not env.Bit('do_not_run_tests'):
501     for action in post_actions:
502       env.AddPostAction(node, action)
503   return node
505 pre_base_env.AddMethod(PPAPIBrowserTester)
508 # Disabled for ARM and MIPS because Chrome binaries for ARM and MIPS are not
509 # available.
510 def PPAPIBrowserTesterIsBroken(env):
511   return env.Bit('build_arm') or env.Bit('build_mips32')
513 pre_base_env.AddMethod(PPAPIBrowserTesterIsBroken)
515 # 3D is disabled everywhere
516 def PPAPIGraphics3DIsBroken(env):
517   return True
519 pre_base_env.AddMethod(PPAPIGraphics3DIsBroken)
522 def AddChromeFilesFromGroup(env, file_group):
523   env['BUILD_SCONSCRIPTS'] = ExtendFileList(
524       env.get('BUILD_SCONSCRIPTS', []),
525       ppapi_scons_files[file_group])
527 pre_base_env.AddMethod(AddChromeFilesFromGroup)