mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-11-04 05:50:31 -05:00 
			
		
		
		
	
		
			
	
	
		
			1325 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			1325 lines
		
	
	
		
			51 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								# Copyright 2002-2005 Vladimir Prus.
							 | 
						||
| 
								 | 
							
								# Copyright 2002-2003 Dave Abrahams.
							 | 
						||
| 
								 | 
							
								# Copyright 2006 Rene Rivera.
							 | 
						||
| 
								 | 
							
								# Distributed under the Boost Software License, Version 1.0.
							 | 
						||
| 
								 | 
							
								# (See accompanying file LICENSE_1_0.txt or copy at
							 | 
						||
| 
								 | 
							
								# http://www.boost.org/LICENSE_1_0.txt)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import TestCmd
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import copy
							 | 
						||
| 
								 | 
							
								import fnmatch
							 | 
						||
| 
								 | 
							
								import glob
							 | 
						||
| 
								 | 
							
								import math
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import os.path
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import shutil
							 | 
						||
| 
								 | 
							
								import StringIO
							 | 
						||
| 
								 | 
							
								import subprocess
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import tempfile
							 | 
						||
| 
								 | 
							
								import time
							 | 
						||
| 
								 | 
							
								import traceback
							 | 
						||
| 
								 | 
							
								import tree
							 | 
						||
| 
								 | 
							
								import types
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from xml.sax.saxutils import escape
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestEnvironmentError(Exception):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								annotations = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def print_annotation(name, value, xml):
							 | 
						||
| 
								 | 
							
								    """Writes some named bits of information about the current test run."""
							 | 
						||
| 
								 | 
							
								    if xml:
							 | 
						||
| 
								 | 
							
								        print escape(name) + " {{{"
							 | 
						||
| 
								 | 
							
								        print escape(value)
							 | 
						||
| 
								 | 
							
								        print "}}}"
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        print name + " {{{"
							 | 
						||
| 
								 | 
							
								        print value
							 | 
						||
| 
								 | 
							
								        print "}}}"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def flush_annotations(xml=0):
							 | 
						||
| 
								 | 
							
								    global annotations
							 | 
						||
| 
								 | 
							
								    for ann in annotations:
							 | 
						||
| 
								 | 
							
								        print_annotation(ann[0], ann[1], xml)
							 | 
						||
| 
								 | 
							
								    annotations = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def clear_annotations():
							 | 
						||
| 
								 | 
							
								    global annotations
							 | 
						||
| 
								 | 
							
								    annotations = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								defer_annotations = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def set_defer_annotations(n):
							 | 
						||
| 
								 | 
							
								    global defer_annotations
							 | 
						||
| 
								 | 
							
								    defer_annotations = n
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def annotate_stack_trace(tb=None):
							 | 
						||
| 
								 | 
							
								    if tb:
							 | 
						||
| 
								 | 
							
								        trace = TestCmd.caller(traceback.extract_tb(tb), 0)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        trace = TestCmd.caller(traceback.extract_stack(), 1)
							 | 
						||
| 
								 | 
							
								    annotation("stacktrace", trace)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def annotation(name, value):
							 | 
						||
| 
								 | 
							
								    """Records an annotation about the test run."""
							 | 
						||
| 
								 | 
							
								    annotations.append((name, value))
							 | 
						||
| 
								 | 
							
								    if not defer_annotations:
							 | 
						||
| 
								 | 
							
								        flush_annotations()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_toolset():
							 | 
						||
| 
								 | 
							
								    toolset = None
							 | 
						||
| 
								 | 
							
								    for arg in sys.argv[1:]:
							 | 
						||
| 
								 | 
							
								        if not arg.startswith("-"):
							 | 
						||
| 
								 | 
							
								            toolset = arg
							 | 
						||
| 
								 | 
							
								    return toolset or "gcc"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Detect the host OS.
							 | 
						||
| 
								 | 
							
								cygwin = hasattr(os, "uname") and os.uname()[0].lower().startswith("cygwin")
							 | 
						||
| 
								 | 
							
								windows = cygwin or os.environ.get("OS", "").lower().startswith("windows")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def prepare_prefixes_and_suffixes(toolset):
							 | 
						||
| 
								 | 
							
								    prepare_suffix_map(toolset)
							 | 
						||
| 
								 | 
							
								    prepare_library_prefix(toolset)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def prepare_suffix_map(toolset):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								      Set up suffix translation performed by the Boost Build testing framework
							 | 
						||
| 
								 | 
							
								    to accomodate different toolsets generating targets of the same type using
							 | 
						||
| 
								 | 
							
								    different filename extensions (suffixes).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    global suffixes
							 | 
						||
| 
								 | 
							
								    suffixes = {}
							 | 
						||
| 
								 | 
							
								    if windows:
							 | 
						||
| 
								 | 
							
								        if toolset == "gcc":
							 | 
						||
| 
								 | 
							
								            suffixes[".lib"] = ".a"  # mingw static libs use suffix ".a".
							 | 
						||
| 
								 | 
							
								            suffixes[".obj"] = ".o"
							 | 
						||
| 
								 | 
							
								        if cygwin:
							 | 
						||
| 
								 | 
							
								            suffixes[".implib"] = ".lib.a"
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            suffixes[".implib"] = ".lib"
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        suffixes[".exe"] = ""
							 | 
						||
| 
								 | 
							
								        suffixes[".dll"] = ".so"
							 | 
						||
| 
								 | 
							
								        suffixes[".lib"] = ".a"
							 | 
						||
| 
								 | 
							
								        suffixes[".obj"] = ".o"
							 | 
						||
| 
								 | 
							
								        suffixes[".implib"] = ".no_implib_files_on_this_platform"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if hasattr(os, "uname") and os.uname()[0] == "Darwin":
							 | 
						||
| 
								 | 
							
								            suffixes[".dll"] = ".dylib"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def prepare_library_prefix(toolset):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								      Setup whether Boost Build is expected to automatically prepend prefixes
							 | 
						||
| 
								 | 
							
								    to its built library targets.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    global lib_prefix
							 | 
						||
| 
								 | 
							
								    lib_prefix = "lib"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    global dll_prefix
							 | 
						||
| 
								 | 
							
								    if cygwin:
							 | 
						||
| 
								 | 
							
								        dll_prefix = "cyg"
							 | 
						||
| 
								 | 
							
								    elif windows and toolset != "gcc":
							 | 
						||
| 
								 | 
							
								        dll_prefix = None
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        dll_prefix = "lib"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def re_remove(sequence, regex):
							 | 
						||
| 
								 | 
							
								    me = re.compile(regex)
							 | 
						||
| 
								 | 
							
								    result = filter(lambda x: me.match(x), sequence)
							 | 
						||
| 
								 | 
							
								    if not result:
							 | 
						||
| 
								 | 
							
								        raise ValueError()
							 | 
						||
| 
								 | 
							
								    for r in result:
							 | 
						||
| 
								 | 
							
								        sequence.remove(r)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def glob_remove(sequence, pattern):
							 | 
						||
| 
								 | 
							
								    result = fnmatch.filter(sequence, pattern)
							 | 
						||
| 
								 | 
							
								    if not result:
							 | 
						||
| 
								 | 
							
								        raise ValueError()
							 | 
						||
| 
								 | 
							
								    for r in result:
							 | 
						||
| 
								 | 
							
								        sequence.remove(r)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Tester(TestCmd.TestCmd):
							 | 
						||
| 
								 | 
							
								    """Main tester class for Boost Build.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Optional arguments:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    `arguments`                   - Arguments passed to the run executable.
							 | 
						||
| 
								 | 
							
								    `executable`                  - Name of the executable to invoke.
							 | 
						||
| 
								 | 
							
								    `match`                       - Function to use for compating actual and
							 | 
						||
| 
								 | 
							
								                                    expected file contents.
							 | 
						||
| 
								 | 
							
								    `boost_build_path`            - Boost build path to be passed to the run
							 | 
						||
| 
								 | 
							
								                                    executable.
							 | 
						||
| 
								 | 
							
								    `translate_suffixes`          - Whether to update suffixes on the the file
							 | 
						||
| 
								 | 
							
								                                    names passed from the test script so they
							 | 
						||
| 
								 | 
							
								                                    match those actually created by the current
							 | 
						||
| 
								 | 
							
								                                    toolset. For example, static library files
							 | 
						||
| 
								 | 
							
								                                    are specified by using the .lib suffix but
							 | 
						||
| 
								 | 
							
								                                    when the "gcc" toolset is used it actually
							 | 
						||
| 
								 | 
							
								                                    creates them using the .a suffix.
							 | 
						||
| 
								 | 
							
								    `pass_toolset`                - Whether the test system should pass the
							 | 
						||
| 
								 | 
							
								                                    specified toolset to the run executable.
							 | 
						||
| 
								 | 
							
								    `use_test_config`             - Whether the test system should tell the run
							 | 
						||
| 
								 | 
							
								                                    executable to read in the test_config.jam
							 | 
						||
| 
								 | 
							
								                                    configuration file.
							 | 
						||
| 
								 | 
							
								    `ignore_toolset_requirements` - Whether the test system should tell the run
							 | 
						||
| 
								 | 
							
								                                    executable to ignore toolset requirements.
							 | 
						||
| 
								 | 
							
								    `workdir`                     - Absolute directory where the test will be
							 | 
						||
| 
								 | 
							
								                                    run from.
							 | 
						||
| 
								 | 
							
								    `pass_d0`                     - If set, when tests are not explicitly run
							 | 
						||
| 
								 | 
							
								                                    in verbose mode, they are run as silent
							 | 
						||
| 
								 | 
							
								                                    (-d0 & --quiet Boost Jam options).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Optional arguments inherited from the base class:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    `description`                 - Test description string displayed in case
							 | 
						||
| 
								 | 
							
								                                    of a failed test.
							 | 
						||
| 
								 | 
							
								    `subdir`                      - List of subdirectories to automatically
							 | 
						||
| 
								 | 
							
								                                    create under the working directory. Each
							 | 
						||
| 
								 | 
							
								                                    subdirectory needs to be specified
							 | 
						||
| 
								 | 
							
								                                    separately, parent coming before its child.
							 | 
						||
| 
								 | 
							
								    `verbose`                     - Flag that may be used to enable more
							 | 
						||
| 
								 | 
							
								                                    verbose test system output. Note that it
							 | 
						||
| 
								 | 
							
								                                    does not also enable more verbose build
							 | 
						||
| 
								 | 
							
								                                    system output like the --verbose command
							 | 
						||
| 
								 | 
							
								                                    line option does.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    def __init__(self, arguments=None, executable="bjam",
							 | 
						||
| 
								 | 
							
								        match=TestCmd.match_exact, boost_build_path=None,
							 | 
						||
| 
								 | 
							
								        translate_suffixes=True, pass_toolset=True, use_test_config=True,
							 | 
						||
| 
								 | 
							
								        ignore_toolset_requirements=True, workdir="", pass_d0=True,
							 | 
						||
| 
								 | 
							
								        **keywords):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        assert arguments.__class__ is not str
							 | 
						||
| 
								 | 
							
								        self.original_workdir = os.getcwd()
							 | 
						||
| 
								 | 
							
								        if workdir and not os.path.isabs(workdir):
							 | 
						||
| 
								 | 
							
								            raise ("Parameter workdir <%s> must point to an absolute "
							 | 
						||
| 
								 | 
							
								                "directory: " % workdir)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.last_build_timestamp = 0
							 | 
						||
| 
								 | 
							
								        self.translate_suffixes = translate_suffixes
							 | 
						||
| 
								 | 
							
								        self.use_test_config = use_test_config
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.toolset = get_toolset()
							 | 
						||
| 
								 | 
							
								        self.pass_toolset = pass_toolset
							 | 
						||
| 
								 | 
							
								        self.ignore_toolset_requirements = ignore_toolset_requirements
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        prepare_prefixes_and_suffixes(pass_toolset and self.toolset or "gcc")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        use_default_bjam = "--default-bjam" in sys.argv
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not use_default_bjam:
							 | 
						||
| 
								 | 
							
								            jam_build_dir = ""
							 | 
						||
| 
								 | 
							
								            if os.name == "nt":
							 | 
						||
| 
								 | 
							
								                jam_build_dir = "bin.ntx86"
							 | 
						||
| 
								 | 
							
								            elif (os.name == "posix") and os.__dict__.has_key("uname"):
							 | 
						||
| 
								 | 
							
								                if os.uname()[0].lower().startswith("cygwin"):
							 | 
						||
| 
								 | 
							
								                    jam_build_dir = "bin.cygwinx86"
							 | 
						||
| 
								 | 
							
								                    if ("TMP" in os.environ and
							 | 
						||
| 
								 | 
							
								                        os.environ["TMP"].find("~") != -1):
							 | 
						||
| 
								 | 
							
								                        print("Setting $TMP to /tmp to get around problem "
							 | 
						||
| 
								 | 
							
								                            "with short path names")
							 | 
						||
| 
								 | 
							
								                        os.environ["TMP"] = "/tmp"
							 | 
						||
| 
								 | 
							
								                elif os.uname()[0] == "Linux":
							 | 
						||
| 
								 | 
							
								                    cpu = os.uname()[4]
							 | 
						||
| 
								 | 
							
								                    if re.match("i.86", cpu):
							 | 
						||
| 
								 | 
							
								                        jam_build_dir = "bin.linuxx86"
							 | 
						||
| 
								 | 
							
								                    else:
							 | 
						||
| 
								 | 
							
								                        jam_build_dir = "bin.linux" + os.uname()[4]
							 | 
						||
| 
								 | 
							
								                elif os.uname()[0] == "SunOS":
							 | 
						||
| 
								 | 
							
								                    jam_build_dir = "bin.solaris"
							 | 
						||
| 
								 | 
							
								                elif os.uname()[0] == "Darwin":
							 | 
						||
| 
								 | 
							
								                    if os.uname()[4] == "i386":
							 | 
						||
| 
								 | 
							
								                        jam_build_dir = "bin.macosxx86"
							 | 
						||
| 
								 | 
							
								                    elif os.uname()[4] == "x86_64":
							 | 
						||
| 
								 | 
							
								                        jam_build_dir = "bin.macosxx86_64"
							 | 
						||
| 
								 | 
							
								                    else:
							 | 
						||
| 
								 | 
							
								                        jam_build_dir = "bin.macosxppc"
							 | 
						||
| 
								 | 
							
								                elif os.uname()[0] == "AIX":
							 | 
						||
| 
								 | 
							
								                    jam_build_dir = "bin.aix"
							 | 
						||
| 
								 | 
							
								                elif os.uname()[0] == "IRIX64":
							 | 
						||
| 
								 | 
							
								                    jam_build_dir = "bin.irix"
							 | 
						||
| 
								 | 
							
								                elif os.uname()[0] == "FreeBSD":
							 | 
						||
| 
								 | 
							
								                    jam_build_dir = "bin.freebsd"
							 | 
						||
| 
								 | 
							
								                elif os.uname()[0] == "OSF1":
							 | 
						||
| 
								 | 
							
								                    jam_build_dir = "bin.osf"
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    raise ("Do not know directory where Jam is built for this "
							 | 
						||
| 
								 | 
							
								                        "system: %s/%s" % (os.name, os.uname()[0]))
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                raise ("Do not know directory where Jam is built for this "
							 | 
						||
| 
								 | 
							
								                    "system: %s" % os.name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Find where jam_src is located. Try for the debug version if it is
							 | 
						||
| 
								 | 
							
								            # lying around.
							 | 
						||
| 
								 | 
							
								            dirs = [os.path.join("..", "src", "engine", jam_build_dir + ".debug"),
							 | 
						||
| 
								 | 
							
								                    os.path.join("..", "src", "engine", jam_build_dir)]
							 | 
						||
| 
								 | 
							
								            for d in dirs:
							 | 
						||
| 
								 | 
							
								                if os.path.exists(d):
							 | 
						||
| 
								 | 
							
								                    jam_build_dir = d
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                print("Cannot find built Boost.Jam")
							 | 
						||
| 
								 | 
							
								                sys.exit(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        verbosity = ["-d0", "--quiet"]
							 | 
						||
| 
								 | 
							
								        if not pass_d0:
							 | 
						||
| 
								 | 
							
								            verbosity = []
							 | 
						||
| 
								 | 
							
								        if "--verbose" in sys.argv:
							 | 
						||
| 
								 | 
							
								            keywords["verbose"] = True
							 | 
						||
| 
								 | 
							
								            verbosity = ["-d+2"]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if boost_build_path is None:
							 | 
						||
| 
								 | 
							
								            boost_build_path = self.original_workdir + "/.."
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        program_list = []
							 | 
						||
| 
								 | 
							
								        if use_default_bjam:
							 | 
						||
| 
								 | 
							
								            program_list.append(executable)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            program_list.append(os.path.join(jam_build_dir, executable))
							 | 
						||
| 
								 | 
							
								        program_list.append('-sBOOST_BUILD_PATH="' + boost_build_path + '"')
							 | 
						||
| 
								 | 
							
								        if verbosity:
							 | 
						||
| 
								 | 
							
								            program_list += verbosity
							 | 
						||
| 
								 | 
							
								        if arguments:
							 | 
						||
| 
								 | 
							
								            program_list += arguments
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        TestCmd.TestCmd.__init__(self, program=program_list, match=match,
							 | 
						||
| 
								 | 
							
								            workdir=workdir, inpath=use_default_bjam, **keywords)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        os.chdir(self.workdir)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def cleanup(self):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            TestCmd.TestCmd.cleanup(self)
							 | 
						||
| 
								 | 
							
								            os.chdir(self.original_workdir)
							 | 
						||
| 
								 | 
							
								        except AttributeError:
							 | 
						||
| 
								 | 
							
								            # When this is called during TestCmd.TestCmd.__del__ we can have
							 | 
						||
| 
								 | 
							
								            # both 'TestCmd' and 'os' unavailable in our scope. Do nothing in
							 | 
						||
| 
								 | 
							
								            # this case.
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Methods that change the working directory's content.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    def set_tree(self, tree_location):
							 | 
						||
| 
								 | 
							
								        # It is not possible to remove the current directory.
							 | 
						||
| 
								 | 
							
								        d = os.getcwd()
							 | 
						||
| 
								 | 
							
								        os.chdir(os.path.dirname(self.workdir))
							 | 
						||
| 
								 | 
							
								        shutil.rmtree(self.workdir, ignore_errors=False)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not os.path.isabs(tree_location):
							 | 
						||
| 
								 | 
							
								            tree_location = os.path.join(self.original_workdir, tree_location)
							 | 
						||
| 
								 | 
							
								        shutil.copytree(tree_location, self.workdir)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        os.chdir(d)
							 | 
						||
| 
								 | 
							
								        def make_writable(unused, dir, entries):
							 | 
						||
| 
								 | 
							
								            for e in entries:
							 | 
						||
| 
								 | 
							
								                name = os.path.join(dir, e)
							 | 
						||
| 
								 | 
							
								                os.chmod(name, os.stat(name).st_mode | 0222)
							 | 
						||
| 
								 | 
							
								        os.path.walk(".", make_writable, None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def write(self, file, content, wait=True):
							 | 
						||
| 
								 | 
							
								        nfile = self.native_file_name(file)
							 | 
						||
| 
								 | 
							
								        self.__makedirs(os.path.dirname(nfile), wait)
							 | 
						||
| 
								 | 
							
								        f = open(nfile, "wb")
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            f.write(content)
							 | 
						||
| 
								 | 
							
								        finally:
							 | 
						||
| 
								 | 
							
								            f.close()
							 | 
						||
| 
								 | 
							
								        self.__ensure_newer_than_last_build(nfile)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def copy(self, src, dst):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            self.write(dst, self.read(src, 1))
							 | 
						||
| 
								 | 
							
								        except:
							 | 
						||
| 
								 | 
							
								            self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def copy_preserving_timestamp(self, src, dst):
							 | 
						||
| 
								 | 
							
								        src_name = self.native_file_name(src)
							 | 
						||
| 
								 | 
							
								        dst_name = self.native_file_name(dst)
							 | 
						||
| 
								 | 
							
								        stats = os.stat(src_name)
							 | 
						||
| 
								 | 
							
								        self.write(dst, self.read(src, 1))
							 | 
						||
| 
								 | 
							
								        os.utime(dst_name, (stats.st_atime, stats.st_mtime))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def touch(self, names, wait=True):
							 | 
						||
| 
								 | 
							
								        if names.__class__ is str:
							 | 
						||
| 
								 | 
							
								            names = [names]
							 | 
						||
| 
								 | 
							
								        for name in names:
							 | 
						||
| 
								 | 
							
								            path = self.native_file_name(name)
							 | 
						||
| 
								 | 
							
								            if wait:
							 | 
						||
| 
								 | 
							
								                self.__ensure_newer_than_last_build(path)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                os.utime(path, None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def rm(self, names):
							 | 
						||
| 
								 | 
							
								        if not type(names) == types.ListType:
							 | 
						||
| 
								 | 
							
								            names = [names]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if names == ["."]:
							 | 
						||
| 
								 | 
							
								            # If we are deleting the entire workspace, there is no need to wait
							 | 
						||
| 
								 | 
							
								            # for a clock tick.
							 | 
						||
| 
								 | 
							
								            self.last_build_timestamp = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Avoid attempts to remove the current directory.
							 | 
						||
| 
								 | 
							
								        os.chdir(self.original_workdir)
							 | 
						||
| 
								 | 
							
								        for name in names:
							 | 
						||
| 
								 | 
							
								            n = glob.glob(self.native_file_name(name))
							 | 
						||
| 
								 | 
							
								            if n: n = n[0]
							 | 
						||
| 
								 | 
							
								            if not n:
							 | 
						||
| 
								 | 
							
								                n = self.glob_file(name.replace("$toolset", self.toolset + "*")
							 | 
						||
| 
								 | 
							
								                    )
							 | 
						||
| 
								 | 
							
								            if n:
							 | 
						||
| 
								 | 
							
								                if os.path.isdir(n):
							 | 
						||
| 
								 | 
							
								                    shutil.rmtree(n, ignore_errors=False)
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    os.unlink(n)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Create working dir root again in case we removed it.
							 | 
						||
| 
								 | 
							
								        if not os.path.exists(self.workdir):
							 | 
						||
| 
								 | 
							
								            os.mkdir(self.workdir)
							 | 
						||
| 
								 | 
							
								        os.chdir(self.workdir)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expand_toolset(self, name):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								          Expands $toolset placeholder in the given file to the name of the
							 | 
						||
| 
								 | 
							
								        toolset currently being tested.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self.write(name, self.read(name).replace("$toolset", self.toolset))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def dump_stdio(self):
							 | 
						||
| 
								 | 
							
								        annotation("STDOUT", self.stdout())
							 | 
						||
| 
								 | 
							
								        annotation("STDERR", self.stderr())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def run_build_system(self, extra_args=None, subdir="", stdout=None,
							 | 
						||
| 
								 | 
							
								        stderr="", status=0, match=None, pass_toolset=None,
							 | 
						||
| 
								 | 
							
								        use_test_config=None, ignore_toolset_requirements=None,
							 | 
						||
| 
								 | 
							
								        expected_duration=None, **kw):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        assert extra_args.__class__ is not str
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if os.path.isabs(subdir):
							 | 
						||
| 
								 | 
							
								            print("You must pass a relative directory to subdir <%s>." % subdir
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.previous_tree, dummy = tree.build_tree(self.workdir)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if match is None:
							 | 
						||
| 
								 | 
							
								            match = self.match
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if pass_toolset is None:
							 | 
						||
| 
								 | 
							
								            pass_toolset = self.pass_toolset
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if use_test_config is None:
							 | 
						||
| 
								 | 
							
								            use_test_config = self.use_test_config
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ignore_toolset_requirements is None:
							 | 
						||
| 
								 | 
							
								            ignore_toolset_requirements = self.ignore_toolset_requirements
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            kw["program"] = []
							 | 
						||
| 
								 | 
							
								            kw["program"] += self.program
							 | 
						||
| 
								 | 
							
								            if extra_args:
							 | 
						||
| 
								 | 
							
								                kw["program"] += extra_args
							 | 
						||
| 
								 | 
							
								            if pass_toolset:
							 | 
						||
| 
								 | 
							
								                kw["program"].append("toolset=" + self.toolset)
							 | 
						||
| 
								 | 
							
								            if use_test_config:
							 | 
						||
| 
								 | 
							
								                kw["program"].append('--test-config="%s"' % os.path.join(
							 | 
						||
| 
								 | 
							
								                    self.original_workdir, "test-config.jam"))
							 | 
						||
| 
								 | 
							
								            if ignore_toolset_requirements:
							 | 
						||
| 
								 | 
							
								                kw["program"].append("--ignore-toolset-requirements")
							 | 
						||
| 
								 | 
							
								            if "--python" in sys.argv:
							 | 
						||
| 
								 | 
							
								                # -z disables Python optimization mode.
							 | 
						||
| 
								 | 
							
								                # this enables type checking (all assert
							 | 
						||
| 
								 | 
							
								                # and if __debug__ statements).
							 | 
						||
| 
								 | 
							
								                kw["program"].extend(["--python", "-z"])
							 | 
						||
| 
								 | 
							
								            if "--stacktrace" in sys.argv:
							 | 
						||
| 
								 | 
							
								                kw["program"].append("--stacktrace")
							 | 
						||
| 
								 | 
							
								            kw["chdir"] = subdir
							 | 
						||
| 
								 | 
							
								            self.last_program_invocation = kw["program"]
							 | 
						||
| 
								 | 
							
								            build_time_start = time.time()
							 | 
						||
| 
								 | 
							
								            apply(TestCmd.TestCmd.run, [self], kw)
							 | 
						||
| 
								 | 
							
								            build_time_finish = time.time()
							 | 
						||
| 
								 | 
							
								        except:
							 | 
						||
| 
								 | 
							
								            self.dump_stdio()
							 | 
						||
| 
								 | 
							
								            raise
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        old_last_build_timestamp = self.last_build_timestamp
							 | 
						||
| 
								 | 
							
								        self.tree, self.last_build_timestamp = tree.build_tree(self.workdir)
							 | 
						||
| 
								 | 
							
								        self.difference = tree.tree_difference(self.previous_tree, self.tree)
							 | 
						||
| 
								 | 
							
								        if self.difference.empty():
							 | 
						||
| 
								 | 
							
								            # If nothing has been changed by this build and sufficient time has
							 | 
						||
| 
								 | 
							
								            # passed since the last build that actually changed something,
							 | 
						||
| 
								 | 
							
								            # there is no need to wait for touched or newly created files to
							 | 
						||
| 
								 | 
							
								            # start getting newer timestamps than the currently existing ones.
							 | 
						||
| 
								 | 
							
								            self.last_build_timestamp = old_last_build_timestamp
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.difference.ignore_directories()
							 | 
						||
| 
								 | 
							
								        self.unexpected_difference = copy.deepcopy(self.difference)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (status and self.status) is not None and self.status != status:
							 | 
						||
| 
								 | 
							
								            expect = ""
							 | 
						||
| 
								 | 
							
								            if status != 0:
							 | 
						||
| 
								 | 
							
								                expect = " (expected %d)" % status
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            annotation("failure", '"%s" returned %d%s' % (kw["program"],
							 | 
						||
| 
								 | 
							
								                self.status, expect))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            annotation("reason", "unexpected status returned by bjam")
							 | 
						||
| 
								 | 
							
								            self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if stdout is not None and not match(self.stdout(), stdout):
							 | 
						||
| 
								 | 
							
								            annotation("failure", "Unexpected stdout")
							 | 
						||
| 
								 | 
							
								            annotation("Expected STDOUT", stdout)
							 | 
						||
| 
								 | 
							
								            annotation("Actual STDOUT", self.stdout())
							 | 
						||
| 
								 | 
							
								            stderr = self.stderr()
							 | 
						||
| 
								 | 
							
								            if stderr:
							 | 
						||
| 
								 | 
							
								                annotation("STDERR", stderr)
							 | 
						||
| 
								 | 
							
								            self.maybe_do_diff(self.stdout(), stdout)
							 | 
						||
| 
								 | 
							
								            self.fail_test(1, dump_stdio=False)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Intel tends to produce some messages to stderr which make tests fail.
							 | 
						||
| 
								 | 
							
								        intel_workaround = re.compile("^xi(link|lib): executing.*\n", re.M)
							 | 
						||
| 
								 | 
							
								        actual_stderr = re.sub(intel_workaround, "", self.stderr())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if stderr is not None and not match(actual_stderr, stderr):
							 | 
						||
| 
								 | 
							
								            annotation("failure", "Unexpected stderr")
							 | 
						||
| 
								 | 
							
								            annotation("Expected STDERR", stderr)
							 | 
						||
| 
								 | 
							
								            annotation("Actual STDERR", self.stderr())
							 | 
						||
| 
								 | 
							
								            annotation("STDOUT", self.stdout())
							 | 
						||
| 
								 | 
							
								            self.maybe_do_diff(actual_stderr, stderr)
							 | 
						||
| 
								 | 
							
								            self.fail_test(1, dump_stdio=False)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if expected_duration is not None:
							 | 
						||
| 
								 | 
							
								            actual_duration = build_time_finish - build_time_start
							 | 
						||
| 
								 | 
							
								            if actual_duration > expected_duration:
							 | 
						||
| 
								 | 
							
								                print("Test run lasted %f seconds while it was expected to "
							 | 
						||
| 
								 | 
							
								                    "finish in under %f seconds." % (actual_duration,
							 | 
						||
| 
								 | 
							
								                    expected_duration))
							 | 
						||
| 
								 | 
							
								                self.fail_test(1, dump_stdio=False)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def glob_file(self, name):
							 | 
						||
| 
								 | 
							
								        result = None
							 | 
						||
| 
								 | 
							
								        if hasattr(self, "difference"):
							 | 
						||
| 
								 | 
							
								            for f in (self.difference.added_files +
							 | 
						||
| 
								 | 
							
								                self.difference.modified_files +
							 | 
						||
| 
								 | 
							
								                self.difference.touched_files):
							 | 
						||
| 
								 | 
							
								                if fnmatch.fnmatch(f, name):
							 | 
						||
| 
								 | 
							
								                    result = self.native_file_name(f)
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								        if not result:
							 | 
						||
| 
								 | 
							
								            result = glob.glob(self.native_file_name(name))
							 | 
						||
| 
								 | 
							
								            if result:
							 | 
						||
| 
								 | 
							
								                result = result[0]
							 | 
						||
| 
								 | 
							
								        return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def read(self, name, binary=False):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            if self.toolset:
							 | 
						||
| 
								 | 
							
								                name = name.replace("$toolset", self.toolset + "*")
							 | 
						||
| 
								 | 
							
								            name = self.glob_file(name)
							 | 
						||
| 
								 | 
							
								            openMode = "r"
							 | 
						||
| 
								 | 
							
								            if binary:
							 | 
						||
| 
								 | 
							
								                openMode += "b"
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                openMode += "U"
							 | 
						||
| 
								 | 
							
								            f = open(name, openMode)
							 | 
						||
| 
								 | 
							
								            result = f.read()
							 | 
						||
| 
								 | 
							
								            f.close()
							 | 
						||
| 
								 | 
							
								            return result
							 | 
						||
| 
								 | 
							
								        except:
							 | 
						||
| 
								 | 
							
								            annotation("failure", "Could not open '%s'" % name)
							 | 
						||
| 
								 | 
							
								            self.fail_test(1)
							 | 
						||
| 
								 | 
							
								            return ""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def read_and_strip(self, name):
							 | 
						||
| 
								 | 
							
								        if not self.glob_file(name):
							 | 
						||
| 
								 | 
							
								            return ""
							 | 
						||
| 
								 | 
							
								        f = open(self.glob_file(name), "rb")
							 | 
						||
| 
								 | 
							
								        lines = f.readlines()
							 | 
						||
| 
								 | 
							
								        f.close()
							 | 
						||
| 
								 | 
							
								        result = "\n".join(x.rstrip() for x in lines)
							 | 
						||
| 
								 | 
							
								        if lines and lines[-1][-1] != "\n":
							 | 
						||
| 
								 | 
							
								            return result + "\n"
							 | 
						||
| 
								 | 
							
								        return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fail_test(self, condition, dump_difference=True, dump_stdio=True,
							 | 
						||
| 
								 | 
							
								        dump_stack=True):
							 | 
						||
| 
								 | 
							
								        if not condition:
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if dump_difference and hasattr(self, "difference"):
							 | 
						||
| 
								 | 
							
								            f = StringIO.StringIO()
							 | 
						||
| 
								 | 
							
								            self.difference.pprint(f)
							 | 
						||
| 
								 | 
							
								            annotation("changes caused by the last build command",
							 | 
						||
| 
								 | 
							
								                f.getvalue())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if dump_stdio:
							 | 
						||
| 
								 | 
							
								            self.dump_stdio()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if "--preserve" in sys.argv:
							 | 
						||
| 
								 | 
							
								            print
							 | 
						||
| 
								 | 
							
								            print "*** Copying the state of working dir into 'failed_test' ***"
							 | 
						||
| 
								 | 
							
								            print
							 | 
						||
| 
								 | 
							
								            path = os.path.join(self.original_workdir, "failed_test")
							 | 
						||
| 
								 | 
							
								            if os.path.isdir(path):
							 | 
						||
| 
								 | 
							
								                shutil.rmtree(path, ignore_errors=False)
							 | 
						||
| 
								 | 
							
								            elif os.path.exists(path):
							 | 
						||
| 
								 | 
							
								                raise "Path " + path + " already exists and is not a directory"
							 | 
						||
| 
								 | 
							
								            shutil.copytree(self.workdir, path)
							 | 
						||
| 
								 | 
							
								            print "The failed command was:"
							 | 
						||
| 
								 | 
							
								            print " ".join(self.last_program_invocation)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if dump_stack:
							 | 
						||
| 
								 | 
							
								            annotate_stack_trace()
							 | 
						||
| 
								 | 
							
								        sys.exit(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # A number of methods below check expectations with actual difference
							 | 
						||
| 
								 | 
							
								    # between directory trees before and after a build. All the 'expect*'
							 | 
						||
| 
								 | 
							
								    # methods require exact names to be passed. All the 'ignore*' methods allow
							 | 
						||
| 
								 | 
							
								    # wildcards.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # All names can be either a string or a list of strings.
							 | 
						||
| 
								 | 
							
								    def expect_addition(self, names):
							 | 
						||
| 
								 | 
							
								        for name in self.adjust_names(names):
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                glob_remove(self.unexpected_difference.added_files, name)
							 | 
						||
| 
								 | 
							
								            except:
							 | 
						||
| 
								 | 
							
								                annotation("failure", "File %s not added as expected" % name)
							 | 
						||
| 
								 | 
							
								                self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def ignore_addition(self, wildcard):
							 | 
						||
| 
								 | 
							
								        self.__ignore_elements(self.unexpected_difference.added_files,
							 | 
						||
| 
								 | 
							
								            wildcard)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expect_removal(self, names):
							 | 
						||
| 
								 | 
							
								        for name in self.adjust_names(names):
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                glob_remove(self.unexpected_difference.removed_files, name)
							 | 
						||
| 
								 | 
							
								            except:
							 | 
						||
| 
								 | 
							
								                annotation("failure", "File %s not removed as expected" % name)
							 | 
						||
| 
								 | 
							
								                self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def ignore_removal(self, wildcard):
							 | 
						||
| 
								 | 
							
								        self.__ignore_elements(self.unexpected_difference.removed_files,
							 | 
						||
| 
								 | 
							
								            wildcard)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expect_modification(self, names):
							 | 
						||
| 
								 | 
							
								        for name in self.adjust_names(names):
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                glob_remove(self.unexpected_difference.modified_files, name)
							 | 
						||
| 
								 | 
							
								            except:
							 | 
						||
| 
								 | 
							
								                annotation("failure", "File %s not modified as expected" %
							 | 
						||
| 
								 | 
							
								                    name)
							 | 
						||
| 
								 | 
							
								                self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def ignore_modification(self, wildcard):
							 | 
						||
| 
								 | 
							
								        self.__ignore_elements(self.unexpected_difference.modified_files,
							 | 
						||
| 
								 | 
							
								            wildcard)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expect_touch(self, names):
							 | 
						||
| 
								 | 
							
								        d = self.unexpected_difference
							 | 
						||
| 
								 | 
							
								        for name in self.adjust_names(names):
							 | 
						||
| 
								 | 
							
								            # We need to check both touched and modified files. The reason is
							 | 
						||
| 
								 | 
							
								            # that:
							 | 
						||
| 
								 | 
							
								            #   (1) Windows binaries such as obj, exe or dll files have slight
							 | 
						||
| 
								 | 
							
								            #       differences even with identical inputs due to Windows PE
							 | 
						||
| 
								 | 
							
								            #       format headers containing an internal timestamp.
							 | 
						||
| 
								 | 
							
								            #   (2) Intel's compiler for Linux has the same behaviour.
							 | 
						||
| 
								 | 
							
								            filesets = [d.modified_files, d.touched_files]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            while filesets:
							 | 
						||
| 
								 | 
							
								                try:
							 | 
						||
| 
								 | 
							
								                    glob_remove(filesets[-1], name)
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								                except ValueError:
							 | 
						||
| 
								 | 
							
								                    filesets.pop()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if not filesets:
							 | 
						||
| 
								 | 
							
								                annotation("failure", "File %s not touched as expected" % name)
							 | 
						||
| 
								 | 
							
								                self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def ignore_touch(self, wildcard):
							 | 
						||
| 
								 | 
							
								        self.__ignore_elements(self.unexpected_difference.touched_files,
							 | 
						||
| 
								 | 
							
								            wildcard)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def ignore(self, wildcard):
							 | 
						||
| 
								 | 
							
								        self.ignore_addition(wildcard)
							 | 
						||
| 
								 | 
							
								        self.ignore_removal(wildcard)
							 | 
						||
| 
								 | 
							
								        self.ignore_modification(wildcard)
							 | 
						||
| 
								 | 
							
								        self.ignore_touch(wildcard)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expect_nothing(self, names):
							 | 
						||
| 
								 | 
							
								        for name in self.adjust_names(names):
							 | 
						||
| 
								 | 
							
								            if name in self.difference.added_files:
							 | 
						||
| 
								 | 
							
								                annotation("failure",
							 | 
						||
| 
								 | 
							
								                    "File %s added, but no action was expected" % name)
							 | 
						||
| 
								 | 
							
								                self.fail_test(1)
							 | 
						||
| 
								 | 
							
								            if name in self.difference.removed_files:
							 | 
						||
| 
								 | 
							
								                annotation("failure",
							 | 
						||
| 
								 | 
							
								                    "File %s removed, but no action was expected" % name)
							 | 
						||
| 
								 | 
							
								                self.fail_test(1)
							 | 
						||
| 
								 | 
							
								                pass
							 | 
						||
| 
								 | 
							
								            if name in self.difference.modified_files:
							 | 
						||
| 
								 | 
							
								                annotation("failure",
							 | 
						||
| 
								 | 
							
								                    "File %s modified, but no action was expected" % name)
							 | 
						||
| 
								 | 
							
								                self.fail_test(1)
							 | 
						||
| 
								 | 
							
								            if name in self.difference.touched_files:
							 | 
						||
| 
								 | 
							
								                annotation("failure",
							 | 
						||
| 
								 | 
							
								                    "File %s touched, but no action was expected" % name)
							 | 
						||
| 
								 | 
							
								                self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expect_nothing_more(self):
							 | 
						||
| 
								 | 
							
								        # Not totally sure about this change, but I do not see a good
							 | 
						||
| 
								 | 
							
								        # alternative.
							 | 
						||
| 
								 | 
							
								        if windows:
							 | 
						||
| 
								 | 
							
								            self.ignore("*.ilk")       # MSVC incremental linking files.
							 | 
						||
| 
								 | 
							
								            self.ignore("*.pdb")       # MSVC program database files.
							 | 
						||
| 
								 | 
							
								            self.ignore("*.rsp")       # Response files.
							 | 
						||
| 
								 | 
							
								            self.ignore("*.tds")       # Borland debug symbols.
							 | 
						||
| 
								 | 
							
								            self.ignore("*.manifest")  # MSVC DLL manifests.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Debug builds of bjam built with gcc produce this profiling data.
							 | 
						||
| 
								 | 
							
								        self.ignore("gmon.out")
							 | 
						||
| 
								 | 
							
								        self.ignore("*/gmon.out")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Boost Build's 'configure' functionality (unfinished at the time)
							 | 
						||
| 
								 | 
							
								        # produces this file.
							 | 
						||
| 
								 | 
							
								        self.ignore("bin/config.log")
							 | 
						||
| 
								 | 
							
								        self.ignore("bin/project-cache.jam")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Compiled Python files created when running Python based Boost Build.
							 | 
						||
| 
								 | 
							
								        self.ignore("*.pyc")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not self.unexpected_difference.empty():
							 | 
						||
| 
								 | 
							
								            annotation("failure", "Unexpected changes found")
							 | 
						||
| 
								 | 
							
								            output = StringIO.StringIO()
							 | 
						||
| 
								 | 
							
								            self.unexpected_difference.pprint(output)
							 | 
						||
| 
								 | 
							
								            annotation("unexpected changes", output.getvalue())
							 | 
						||
| 
								 | 
							
								            self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expect_output_lines(self, lines, expected=True):
							 | 
						||
| 
								 | 
							
								        self.__expect_lines(self.stdout(), lines, expected)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expect_content_lines(self, filename, line, expected=True):
							 | 
						||
| 
								 | 
							
								        self.__expect_lines(self.__read_file(filename), line, expected)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def expect_content(self, name, content, exact=False):
							 | 
						||
| 
								 | 
							
								        actual = self.__read_file(name, exact)
							 | 
						||
| 
								 | 
							
								        content = content.replace("$toolset", self.toolset + "*")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        matched = False
							 | 
						||
| 
								 | 
							
								        if exact:
							 | 
						||
| 
								 | 
							
								            matched = fnmatch.fnmatch(actual, content)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            def sorted_(x):
							 | 
						||
| 
								 | 
							
								                x.sort()
							 | 
						||
| 
								 | 
							
								                return x
							 | 
						||
| 
								 | 
							
								            actual_ = map(lambda x: sorted_(x.split()), actual.splitlines())
							 | 
						||
| 
								 | 
							
								            content_ = map(lambda x: sorted_(x.split()), content.splitlines())
							 | 
						||
| 
								 | 
							
								            if len(actual_) == len(content_):
							 | 
						||
| 
								 | 
							
								                matched = map(
							 | 
						||
| 
								 | 
							
								                    lambda x, y: map(lambda n, p: fnmatch.fnmatch(n, p), x, y),
							 | 
						||
| 
								 | 
							
								                    actual_, content_)
							 | 
						||
| 
								 | 
							
								                matched = reduce(
							 | 
						||
| 
								 | 
							
								                    lambda x, y: x and reduce(
							 | 
						||
| 
								 | 
							
								                        lambda a, b: a and b,
							 | 
						||
| 
								 | 
							
								                    y),
							 | 
						||
| 
								 | 
							
								                    matched)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not matched:
							 | 
						||
| 
								 | 
							
								            print "Expected:\n"
							 | 
						||
| 
								 | 
							
								            print content
							 | 
						||
| 
								 | 
							
								            print "Got:\n"
							 | 
						||
| 
								 | 
							
								            print actual
							 | 
						||
| 
								 | 
							
								            self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def maybe_do_diff(self, actual, expected):
							 | 
						||
| 
								 | 
							
								        if os.environ.get("DO_DIFF"):
							 | 
						||
| 
								 | 
							
								            e = tempfile.mktemp("expected")
							 | 
						||
| 
								 | 
							
								            a = tempfile.mktemp("actual")
							 | 
						||
| 
								 | 
							
								            f = open(e, "w")
							 | 
						||
| 
								 | 
							
								            f.write(expected)
							 | 
						||
| 
								 | 
							
								            f.close()
							 | 
						||
| 
								 | 
							
								            f = open(a, "w")
							 | 
						||
| 
								 | 
							
								            f.write(actual)
							 | 
						||
| 
								 | 
							
								            f.close()
							 | 
						||
| 
								 | 
							
								            print("DIFFERENCE")
							 | 
						||
| 
								 | 
							
								            # Current diff should return 1 to indicate 'different input files'
							 | 
						||
| 
								 | 
							
								            # but some older diff versions may return 0 and depending on the
							 | 
						||
| 
								 | 
							
								            # exact Python/OS platform version, os.system() call may gobble up
							 | 
						||
| 
								 | 
							
								            # the external process's return code and return 0 itself.
							 | 
						||
| 
								 | 
							
								            if os.system('diff -u "%s" "%s"' % (e, a)) not in [0, 1]:
							 | 
						||
| 
								 | 
							
								                print('Unable to compute difference: diff -u "%s" "%s"' % (e, a
							 | 
						||
| 
								 | 
							
								                    ))
							 | 
						||
| 
								 | 
							
								            os.unlink(e)
							 | 
						||
| 
								 | 
							
								            os.unlink(a)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            print("Set environmental variable 'DO_DIFF' to examine the "
							 | 
						||
| 
								 | 
							
								                "difference.")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Internal methods.
							 | 
						||
| 
								 | 
							
								    def adjust_lib_name(self, name):
							 | 
						||
| 
								 | 
							
								        global lib_prefix
							 | 
						||
| 
								 | 
							
								        global dll_prefix
							 | 
						||
| 
								 | 
							
								        result = name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        pos = name.rfind(".")
							 | 
						||
| 
								 | 
							
								        if pos != -1:
							 | 
						||
| 
								 | 
							
								            suffix = name[pos:]
							 | 
						||
| 
								 | 
							
								            if suffix == ".lib":
							 | 
						||
| 
								 | 
							
								                (head, tail) = os.path.split(name)
							 | 
						||
| 
								 | 
							
								                if lib_prefix:
							 | 
						||
| 
								 | 
							
								                    tail = lib_prefix + tail
							 | 
						||
| 
								 | 
							
								                    result = os.path.join(head, tail)
							 | 
						||
| 
								 | 
							
								            elif suffix == ".dll":
							 | 
						||
| 
								 | 
							
								                (head, tail) = os.path.split(name)
							 | 
						||
| 
								 | 
							
								                if dll_prefix:
							 | 
						||
| 
								 | 
							
								                    tail = dll_prefix + tail
							 | 
						||
| 
								 | 
							
								                    result = os.path.join(head, tail)
							 | 
						||
| 
								 | 
							
								        # If we want to use this name in a Jamfile, we better convert \ to /,
							 | 
						||
| 
								 | 
							
								        # as otherwise we would have to quote \.
							 | 
						||
| 
								 | 
							
								        result = result.replace("\\", "/")
							 | 
						||
| 
								 | 
							
								        return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def adjust_suffix(self, name):
							 | 
						||
| 
								 | 
							
								        if not self.translate_suffixes:
							 | 
						||
| 
								 | 
							
								            return name
							 | 
						||
| 
								 | 
							
								        pos = name.rfind(".")
							 | 
						||
| 
								 | 
							
								        if pos == -1:
							 | 
						||
| 
								 | 
							
								            return name
							 | 
						||
| 
								 | 
							
								        suffix = name[pos:]
							 | 
						||
| 
								 | 
							
								        return name[:pos] + suffixes.get(suffix, suffix)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Acceps either a string or a list of strings and returns a list of
							 | 
						||
| 
								 | 
							
								    # strings. Adjusts suffixes on all names.
							 | 
						||
| 
								 | 
							
								    def adjust_names(self, names):
							 | 
						||
| 
								 | 
							
								        if names.__class__ is str:
							 | 
						||
| 
								 | 
							
								            names = [names]
							 | 
						||
| 
								 | 
							
								        r = map(self.adjust_lib_name, names)
							 | 
						||
| 
								 | 
							
								        r = map(self.adjust_suffix, r)
							 | 
						||
| 
								 | 
							
								        r = map(lambda x, t=self.toolset: x.replace("$toolset", t + "*"), r)
							 | 
						||
| 
								 | 
							
								        return r
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def native_file_name(self, name):
							 | 
						||
| 
								 | 
							
								        name = self.adjust_names(name)[0]
							 | 
						||
| 
								 | 
							
								        return os.path.normpath(os.path.join(self.workdir, *name.split("/")))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def wait_for_time_change(self, path, touch):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								          Wait for newly assigned file system modification timestamps for the
							 | 
						||
| 
								 | 
							
								        given path to become large enough for the timestamp difference to be
							 | 
						||
| 
								 | 
							
								        correctly recognized by both this Python based testing framework and
							 | 
						||
| 
								 | 
							
								        the Boost Jam executable being tested. May optionally touch the given
							 | 
						||
| 
								 | 
							
								        path to set its modification timestamp to the new value.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self.__wait_for_time_change(path, touch, last_build_time=False)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __build_timestamp_resolution(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								          Returns the minimum path modification timestamp resolution supported
							 | 
						||
| 
								 | 
							
								        by the used Boost Jam executable.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        dir = tempfile.mkdtemp("bjam_version_info")
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            jam_script = "timestamp_resolution.jam"
							 | 
						||
| 
								 | 
							
								            f = open(os.path.join(dir, jam_script), "w")
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                f.write("EXIT $(JAM_TIMESTAMP_RESOLUTION) : 0 ;")
							 | 
						||
| 
								 | 
							
								            finally:
							 | 
						||
| 
								 | 
							
								                f.close()
							 | 
						||
| 
								 | 
							
								            p = subprocess.Popen([self.program[0], "-d0", "-f%s" % jam_script],
							 | 
						||
| 
								 | 
							
								                stdout=subprocess.PIPE, cwd=dir, universal_newlines=True)
							 | 
						||
| 
								 | 
							
								            out, err = p.communicate()
							 | 
						||
| 
								 | 
							
								        finally:
							 | 
						||
| 
								 | 
							
								            shutil.rmtree(dir, ignore_errors=False)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if p.returncode != 0:
							 | 
						||
| 
								 | 
							
								            raise TestEnvironmentError("Unexpected return code (%s) when "
							 | 
						||
| 
								 | 
							
								                "detecting Boost Jam's minimum supported path modification "
							 | 
						||
| 
								 | 
							
								                "timestamp resolution version information." % p.returncode)
							 | 
						||
| 
								 | 
							
								        if err:
							 | 
						||
| 
								 | 
							
								            raise TestEnvironmentError("Unexpected error output (%s) when "
							 | 
						||
| 
								 | 
							
								                "detecting Boost Jam's minimum supported path modification "
							 | 
						||
| 
								 | 
							
								                "timestamp resolution version information." % err)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        r = re.match("([0-9]{2}):([0-9]{2}):([0-9]{2}\\.[0-9]{9})$", out)
							 | 
						||
| 
								 | 
							
								        if not r:
							 | 
						||
| 
								 | 
							
								            # Older Boost Jam versions did not report their minimum supported
							 | 
						||
| 
								 | 
							
								            # path modification timestamp resolution and did not actually
							 | 
						||
| 
								 | 
							
								            # support path modification timestamp resolutions finer than 1
							 | 
						||
| 
								 | 
							
								            # second.
							 | 
						||
| 
								 | 
							
								            # TODO: Phase this support out to avoid such fallback code from
							 | 
						||
| 
								 | 
							
								            # possibly covering up other problems.
							 | 
						||
| 
								 | 
							
								            return 1
							 | 
						||
| 
								 | 
							
								        if r.group(1) != "00" or r.group(2) != "00":  # hours, minutes
							 | 
						||
| 
								 | 
							
								            raise TestEnvironmentError("Boost Jam with too coarse minimum "
							 | 
						||
| 
								 | 
							
								                "supported path modification timestamp resolution (%s:%s:%s)."
							 | 
						||
| 
								 | 
							
								                % (r.group(1), r.group(2), r.group(3)))
							 | 
						||
| 
								 | 
							
								        return float(r.group(3))  # seconds.nanoseconds
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __ensure_newer_than_last_build(self, path):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								          Updates the given path's modification timestamp after waiting for the
							 | 
						||
| 
								 | 
							
								        newly assigned file system modification timestamp to become large
							 | 
						||
| 
								 | 
							
								        enough for the timestamp difference between it and the last build
							 | 
						||
| 
								 | 
							
								        timestamp to be correctly recognized by both this Python based testing
							 | 
						||
| 
								 | 
							
								        framework and the Boost Jam executable being tested. Does nothing if
							 | 
						||
| 
								 | 
							
								        there is no 'last build' information available.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if self.last_build_timestamp:
							 | 
						||
| 
								 | 
							
								            self.__wait_for_time_change(path, touch=True, last_build_time=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __expect_lines(self, data, lines, expected):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								          Checks whether the given data contains the given lines.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          Data may be specified as a single string containing text lines
							 | 
						||
| 
								 | 
							
								        separated by newline characters.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          Lines may be specified in any of the following forms:
							 | 
						||
| 
								 | 
							
								            * Single string containing text lines separated by newlines - the
							 | 
						||
| 
								 | 
							
								              given lines are searched for in the given data without any extra
							 | 
						||
| 
								 | 
							
								              data lines between them.
							 | 
						||
| 
								 | 
							
								            * Container of strings containing text lines separated by newlines
							 | 
						||
| 
								 | 
							
								              - the given lines are searched for in the given data with extra
							 | 
						||
| 
								 | 
							
								              data lines allowed between lines belonging to different strings.
							 | 
						||
| 
								 | 
							
								            * Container of strings containing text lines separated by newlines
							 | 
						||
| 
								 | 
							
								              and containers containing strings - the same as above with the
							 | 
						||
| 
								 | 
							
								              internal containers containing strings being interpreted as if
							 | 
						||
| 
								 | 
							
								              all their content was joined together into a single string
							 | 
						||
| 
								 | 
							
								              separated by newlines.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          A newline at the end of any multi-line lines string is interpreted as
							 | 
						||
| 
								 | 
							
								        an expected extra trailig empty line.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # str.splitlines() trims at most one trailing newline while we want the
							 | 
						||
| 
								 | 
							
								        # trailing newline to indicate that there should be an extra empty line
							 | 
						||
| 
								 | 
							
								        # at the end.
							 | 
						||
| 
								 | 
							
								        splitlines = lambda x : (x + "\n").splitlines()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if data is None:
							 | 
						||
| 
								 | 
							
								            data = []
							 | 
						||
| 
								 | 
							
								        elif data.__class__ is str:
							 | 
						||
| 
								 | 
							
								            data = splitlines(data)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if lines.__class__ is str:
							 | 
						||
| 
								 | 
							
								            lines = [splitlines(lines)]
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            expanded = []
							 | 
						||
| 
								 | 
							
								            for x in lines:
							 | 
						||
| 
								 | 
							
								                if x.__class__ is str:
							 | 
						||
| 
								 | 
							
								                    x = splitlines(x)
							 | 
						||
| 
								 | 
							
								                expanded.append(x)
							 | 
						||
| 
								 | 
							
								            lines = expanded
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if _contains_lines(data, lines) != bool(expected):
							 | 
						||
| 
								 | 
							
								            output = []
							 | 
						||
| 
								 | 
							
								            if expected:
							 | 
						||
| 
								 | 
							
								                output = ["Did not find expected lines:"]
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                output = ["Found unexpected lines:"]
							 | 
						||
| 
								 | 
							
								            first = True
							 | 
						||
| 
								 | 
							
								            for line_sequence in lines:
							 | 
						||
| 
								 | 
							
								                if line_sequence:
							 | 
						||
| 
								 | 
							
								                    if first:
							 | 
						||
| 
								 | 
							
								                        first = False
							 | 
						||
| 
								 | 
							
								                    else:
							 | 
						||
| 
								 | 
							
								                        output.append("...")
							 | 
						||
| 
								 | 
							
								                    output.extend("  > " + line for line in line_sequence)
							 | 
						||
| 
								 | 
							
								            output.append("in output:")
							 | 
						||
| 
								 | 
							
								            output.extend("  > " + line for line in data)
							 | 
						||
| 
								 | 
							
								            annotation("failure", "\n".join(output))
							 | 
						||
| 
								 | 
							
								            self.fail_test(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __ignore_elements(self, list, wildcard):
							 | 
						||
| 
								 | 
							
								        """Removes in-place 'list' elements matching the given 'wildcard'."""
							 | 
						||
| 
								 | 
							
								        list[:] = filter(lambda x, w=wildcard: not fnmatch.fnmatch(x, w), list)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __makedirs(self, path, wait):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								          Creates a folder with the given path, together with any missing
							 | 
						||
| 
								 | 
							
								        parent folders. If WAIT is set, makes sure any newly created folders
							 | 
						||
| 
								 | 
							
								        have modification timestamps newer than the ones left behind by the
							 | 
						||
| 
								 | 
							
								        last build run.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            if wait:
							 | 
						||
| 
								 | 
							
								                stack = []
							 | 
						||
| 
								 | 
							
								                while path and path not in stack and not os.path.isdir(path):
							 | 
						||
| 
								 | 
							
								                    stack.append(path)
							 | 
						||
| 
								 | 
							
								                    path = os.path.dirname(path)
							 | 
						||
| 
								 | 
							
								                while stack:
							 | 
						||
| 
								 | 
							
								                    path = stack.pop()
							 | 
						||
| 
								 | 
							
								                    os.mkdir(path)
							 | 
						||
| 
								 | 
							
								                    self.__ensure_newer_than_last_build(path)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                os.makedirs(path)
							 | 
						||
| 
								 | 
							
								        except Exception:
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __python_timestamp_resolution(self, path, minimum_resolution):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								          Returns the modification timestamp resolution for the given path
							 | 
						||
| 
								 | 
							
								        supported by the used Python interpreter/OS/filesystem combination.
							 | 
						||
| 
								 | 
							
								        Will not check for resolutions less than the given minimum value. Will
							 | 
						||
| 
								 | 
							
								        change the path's modification timestamp in the process.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          Return values:
							 | 
						||
| 
								 | 
							
								            0                - nanosecond resolution supported
							 | 
						||
| 
								 | 
							
								            positive decimal - timestamp resolution in seconds
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # Note on Python's floating point timestamp support:
							 | 
						||
| 
								 | 
							
								        #   Python interpreter versions prior to Python 2.3 did not support
							 | 
						||
| 
								 | 
							
								        # floating point timestamps. Versions 2.3 through 3.3 may or may not
							 | 
						||
| 
								 | 
							
								        # support it depending on the configuration (may be toggled by calling
							 | 
						||
| 
								 | 
							
								        # os.stat_float_times(True/False) at program startup, disabled by
							 | 
						||
| 
								 | 
							
								        # default prior to Python 2.5 and enabled by default since). Python 3.3
							 | 
						||
| 
								 | 
							
								        # deprecated this configuration and 3.4 removed support for it after
							 | 
						||
| 
								 | 
							
								        # which floating point timestamps are always supported.
							 | 
						||
| 
								 | 
							
								        ver = sys.version_info[0:2]
							 | 
						||
| 
								 | 
							
								        python_nanosecond_support = ver >= (3, 4) or (ver >= (2, 3) and
							 | 
						||
| 
								 | 
							
								            os.stat_float_times())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Minimal expected floating point difference used to account for
							 | 
						||
| 
								 | 
							
								        # possible imprecise floating point number representations. We want
							 | 
						||
| 
								 | 
							
								        # this number to be small (at least smaller than 0.0001) but still
							 | 
						||
| 
								 | 
							
								        # large enough that we can be sure that increasing a floating point
							 | 
						||
| 
								 | 
							
								        # value by 2 * eta guarantees the value read back will be increased by
							 | 
						||
| 
								 | 
							
								        # at least eta.
							 | 
						||
| 
								 | 
							
								        eta = 0.00005
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        stats_orig = os.stat(path)
							 | 
						||
| 
								 | 
							
								        def test_time(diff):
							 | 
						||
| 
								 | 
							
								            """Returns whether a timestamp difference is detectable."""
							 | 
						||
| 
								 | 
							
								            os.utime(path, (stats_orig.st_atime, stats_orig.st_mtime + diff))
							 | 
						||
| 
								 | 
							
								            return os.stat(path).st_mtime > stats_orig.st_mtime + eta
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Test for nanosecond timestamp resolution support.
							 | 
						||
| 
								 | 
							
								        if not minimum_resolution and python_nanosecond_support:
							 | 
						||
| 
								 | 
							
								            if test_time(2 * eta):
							 | 
						||
| 
								 | 
							
								                return 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Detect the filesystem timestamp resolution. Note that there is no
							 | 
						||
| 
								 | 
							
								        # need to make this code 'as fast as possible' as, this function gets
							 | 
						||
| 
								 | 
							
								        # called before having to sleep until the next detectable modification
							 | 
						||
| 
								 | 
							
								        # timestamp value and that, since we already know nanosecond resolution
							 | 
						||
| 
								 | 
							
								        # is not supported, will surely take longer than whatever we do here to
							 | 
						||
| 
								 | 
							
								        # detect this minimal detectable modification timestamp resolution.
							 | 
						||
| 
								 | 
							
								        step = 0.1
							 | 
						||
| 
								 | 
							
								        if not python_nanosecond_support:
							 | 
						||
| 
								 | 
							
								            # If Python does not support nanosecond timestamp resolution we
							 | 
						||
| 
								 | 
							
								            # know the minimum possible supported timestamp resolution is 1
							 | 
						||
| 
								 | 
							
								            # second.
							 | 
						||
| 
								 | 
							
								            minimum_resolution = max(1, minimum_resolution)
							 | 
						||
| 
								 | 
							
								        index = max(1, int(minimum_resolution / step))
							 | 
						||
| 
								 | 
							
								        while step * index < minimum_resolution:
							 | 
						||
| 
								 | 
							
								            # Floating point number representation errors may cause our
							 | 
						||
| 
								 | 
							
								            # initially calculated start index to be too small if calculated
							 | 
						||
| 
								 | 
							
								            # directly.
							 | 
						||
| 
								 | 
							
								            index += 1
							 | 
						||
| 
								 | 
							
								        while True:
							 | 
						||
| 
								 | 
							
								            # Do not simply add up the steps to avoid cumulative floating point
							 | 
						||
| 
								 | 
							
								            # number representation errors.
							 | 
						||
| 
								 | 
							
								            next = step * index
							 | 
						||
| 
								 | 
							
								            if next > 10:
							 | 
						||
| 
								 | 
							
								                raise TestEnvironmentError("File systems with too coarse "
							 | 
						||
| 
								 | 
							
								                    "modification timestamp resolutions not supported.")
							 | 
						||
| 
								 | 
							
								            if test_time(next):
							 | 
						||
| 
								 | 
							
								                return next
							 | 
						||
| 
								 | 
							
								            index += 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __read_file(self, name, exact=False):
							 | 
						||
| 
								 | 
							
								        name = self.adjust_names(name)[0]
							 | 
						||
| 
								 | 
							
								        result = ""
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            if exact:
							 | 
						||
| 
								 | 
							
								                result = self.read(name)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                result = self.read_and_strip(name).replace("\\", "/")
							 | 
						||
| 
								 | 
							
								        except (IOError, IndexError):
							 | 
						||
| 
								 | 
							
								            print "Note: could not open file", name
							 | 
						||
| 
								 | 
							
								            self.fail_test(1)
							 | 
						||
| 
								 | 
							
								        return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __wait_for_time_change(self, path, touch, last_build_time):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								          Wait until a newly assigned file system modification timestamp for
							 | 
						||
| 
								 | 
							
								        the given path is large enough for the timestamp difference between it
							 | 
						||
| 
								 | 
							
								        and the last build timestamp or the path's original file system
							 | 
						||
| 
								 | 
							
								        modification timestamp (depending on the last_build_time flag) to be
							 | 
						||
| 
								 | 
							
								        correctly recognized by both this Python based testing framework and
							 | 
						||
| 
								 | 
							
								        the Boost Jam executable being tested. May optionally touch the given
							 | 
						||
| 
								 | 
							
								        path to set its modification timestamp to the new value.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert self.last_build_timestamp or not last_build_time
							 | 
						||
| 
								 | 
							
								        stats_orig = os.stat(path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if last_build_time:
							 | 
						||
| 
								 | 
							
								            start_time = self.last_build_timestamp
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            start_time = stats_orig.st_mtime
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        build_resolution = self.__build_timestamp_resolution()
							 | 
						||
| 
								 | 
							
								        assert build_resolution >= 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Check whether the current timestamp is already new enough.
							 | 
						||
| 
								 | 
							
								        if stats_orig.st_mtime > start_time and (not build_resolution or
							 | 
						||
| 
								 | 
							
								            stats_orig.st_mtime >= start_time + build_resolution):
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        resolution = self.__python_timestamp_resolution(path, build_resolution)
							 | 
						||
| 
								 | 
							
								        assert resolution >= build_resolution
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Implementation notes:
							 | 
						||
| 
								 | 
							
								        #  * Theoretically time.sleep() API might get interrupted too soon
							 | 
						||
| 
								 | 
							
								        #    (never actually encountered).
							 | 
						||
| 
								 | 
							
								        #  * We encountered cases where we sleep just long enough for the
							 | 
						||
| 
								 | 
							
								        #    filesystem's modifiction timestamp to change to the desired value,
							 | 
						||
| 
								 | 
							
								        #    but after waking up, the read timestamp is still just a tiny bit
							 | 
						||
| 
								 | 
							
								        #    too small (encountered on Windows). This is most likely caused by
							 | 
						||
| 
								 | 
							
								        #    imprecise floating point timestamp & sleep interval representation
							 | 
						||
| 
								 | 
							
								        #    used by Python. Note though that we never encountered a case where
							 | 
						||
| 
								 | 
							
								        #    more than one additional tiny sleep() call was needed to remedy
							 | 
						||
| 
								 | 
							
								        #    the situation.
							 | 
						||
| 
								 | 
							
								        #  * We try to wait long enough for the timestamp to change, but do not
							 | 
						||
| 
								 | 
							
								        #    want to waste processing time by waiting too long. The main
							 | 
						||
| 
								 | 
							
								        #    problem is that when we have a coarse resolution, the actual times
							 | 
						||
| 
								 | 
							
								        #    get rounded and we do not know the exact sleep time needed for the
							 | 
						||
| 
								 | 
							
								        #    difference between two such times to pass. E.g. if we have a 1
							 | 
						||
| 
								 | 
							
								        #    second resolution and the original and the current file timestamps
							 | 
						||
| 
								 | 
							
								        #    are both 10 seconds then it could be that the current time is
							 | 
						||
| 
								 | 
							
								        #    10.99 seconds and that we can wait for just one hundredth of a
							 | 
						||
| 
								 | 
							
								        #    second for the current file timestamp to reach its next value, and
							 | 
						||
| 
								 | 
							
								        #    using a longer sleep interval than that would just be wasting
							 | 
						||
| 
								 | 
							
								        #    time.
							 | 
						||
| 
								 | 
							
								        while True:
							 | 
						||
| 
								 | 
							
								            os.utime(path, None)
							 | 
						||
| 
								 | 
							
								            c = os.stat(path).st_mtime
							 | 
						||
| 
								 | 
							
								            if resolution:
							 | 
						||
| 
								 | 
							
								                if c > start_time and (not build_resolution or c >= start_time
							 | 
						||
| 
								 | 
							
								                    + build_resolution):
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								                if c <= start_time - resolution:
							 | 
						||
| 
								 | 
							
								                    # Move close to the desired timestamp in one sleep, but not
							 | 
						||
| 
								 | 
							
								                    # close enough for timestamp rounding to potentially cause
							 | 
						||
| 
								 | 
							
								                    # us to wait too long.
							 | 
						||
| 
								 | 
							
								                    if start_time - c > 5:
							 | 
						||
| 
								 | 
							
								                        if last_build_time:
							 | 
						||
| 
								 | 
							
								                            error_message = ("Last build time recorded as "
							 | 
						||
| 
								 | 
							
								                                "being a future event, causing a too long "
							 | 
						||
| 
								 | 
							
								                                "wait period. Something must have played "
							 | 
						||
| 
								 | 
							
								                                "around with the system clock.")
							 | 
						||
| 
								 | 
							
								                        else:
							 | 
						||
| 
								 | 
							
								                            error_message = ("Original path modification "
							 | 
						||
| 
								 | 
							
								                                "timestamp set to far into the future or "
							 | 
						||
| 
								 | 
							
								                                "something must have played around with the "
							 | 
						||
| 
								 | 
							
								                                "system clock, causing a too long wait "
							 | 
						||
| 
								 | 
							
								                                "period.\nPath: '%s'" % path)
							 | 
						||
| 
								 | 
							
								                        raise TestEnvironmentError(message)
							 | 
						||
| 
								 | 
							
								                    _sleep(start_time - c)
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    # We are close to the desired timestamp so take baby sleeps
							 | 
						||
| 
								 | 
							
								                    # to avoid sleeping too long.
							 | 
						||
| 
								 | 
							
								                    _sleep(max(0.01, resolution / 10))
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                if c > start_time:
							 | 
						||
| 
								 | 
							
								                    break
							 | 
						||
| 
								 | 
							
								                _sleep(max(0.01, start_time - c))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not touch:
							 | 
						||
| 
								 | 
							
								            os.utime(path, (stats_orig.st_atime, stats_orig.st_mtime))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class List:
							 | 
						||
| 
								 | 
							
								    def __init__(self, s=""):
							 | 
						||
| 
								 | 
							
								        elements = []
							 | 
						||
| 
								 | 
							
								        if s.__class__ is str:
							 | 
						||
| 
								 | 
							
								            # Have to handle escaped spaces correctly.
							 | 
						||
| 
								 | 
							
								            elements = s.replace("\ ", "\001").split()
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            elements = s
							 | 
						||
| 
								 | 
							
								        self.l = [e.replace("\001", " ") for e in elements]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __len__(self):
							 | 
						||
| 
								 | 
							
								        return len(self.l)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getitem__(self, key):
							 | 
						||
| 
								 | 
							
								        return self.l[key]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __setitem__(self, key, value):
							 | 
						||
| 
								 | 
							
								        self.l[key] = value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __delitem__(self, key):
							 | 
						||
| 
								 | 
							
								        del self.l[key]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __str__(self):
							 | 
						||
| 
								 | 
							
								        return str(self.l)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self):
							 | 
						||
| 
								 | 
							
								        return "%s.List(%r)" % (self.__module__, " ".join(self.l))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __mul__(self, other):
							 | 
						||
| 
								 | 
							
								        result = List()
							 | 
						||
| 
								 | 
							
								        if not isinstance(other, List):
							 | 
						||
| 
								 | 
							
								            other = List(other)
							 | 
						||
| 
								 | 
							
								        for f in self:
							 | 
						||
| 
								 | 
							
								            for s in other:
							 | 
						||
| 
								 | 
							
								                result.l.append(f + s)
							 | 
						||
| 
								 | 
							
								        return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __rmul__(self, other):
							 | 
						||
| 
								 | 
							
								        if not isinstance(other, List):
							 | 
						||
| 
								 | 
							
								            other = List(other)
							 | 
						||
| 
								 | 
							
								        return List.__mul__(other, self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __add__(self, other):
							 | 
						||
| 
								 | 
							
								        result = List()
							 | 
						||
| 
								 | 
							
								        result.l = self.l[:] + other.l[:]
							 | 
						||
| 
								 | 
							
								        return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _contains_lines(data, lines):
							 | 
						||
| 
								 | 
							
								    data_line_count = len(data)
							 | 
						||
| 
								 | 
							
								    expected_line_count = reduce(lambda x, y: x + len(y), lines, 0)
							 | 
						||
| 
								 | 
							
								    index = 0
							 | 
						||
| 
								 | 
							
								    for expected in lines:
							 | 
						||
| 
								 | 
							
								        if expected_line_count > data_line_count - index:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        expected_line_count -= len(expected)
							 | 
						||
| 
								 | 
							
								        index = _match_line_sequence(data, index, data_line_count -
							 | 
						||
| 
								 | 
							
								            expected_line_count, expected)
							 | 
						||
| 
								 | 
							
								        if index < 0:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								    return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _match_line_sequence(data, start, end, lines):
							 | 
						||
| 
								 | 
							
								    if not lines:
							 | 
						||
| 
								 | 
							
								        return start
							 | 
						||
| 
								 | 
							
								    for index in xrange(start, end - len(lines) + 1):
							 | 
						||
| 
								 | 
							
								        data_index = index
							 | 
						||
| 
								 | 
							
								        for expected in lines:
							 | 
						||
| 
								 | 
							
								            if not fnmatch.fnmatch(data[data_index], expected):
							 | 
						||
| 
								 | 
							
								                break;
							 | 
						||
| 
								 | 
							
								            data_index += 1
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return data_index
							 | 
						||
| 
								 | 
							
								    return -1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _sleep(delay):
							 | 
						||
| 
								 | 
							
								    if delay > 5:
							 | 
						||
| 
								 | 
							
								        raise TestEnvironmentError("Test environment error: sleep period of "
							 | 
						||
| 
								 | 
							
								            "more than 5 seconds requested. Most likely caused by a file with "
							 | 
						||
| 
								 | 
							
								            "its modification timestamp set to sometime in the future.")
							 | 
						||
| 
								 | 
							
								    time.sleep(delay)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								###############################################################################
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Initialization.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								###############################################################################
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Make os.stat() return file modification times as floats instead of integers
							 | 
						||
| 
								 | 
							
								# to get the best possible file timestamp resolution available. The exact
							 | 
						||
| 
								 | 
							
								# resolution depends on the underlying file system and the Python os.stat()
							 | 
						||
| 
								 | 
							
								# implementation. The better the resolution we achieve, the shorter we need to
							 | 
						||
| 
								 | 
							
								# wait for files we create to start getting new timestamps.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Additional notes:
							 | 
						||
| 
								 | 
							
								#  * os.stat_float_times() function first introduced in Python 2.3. and
							 | 
						||
| 
								 | 
							
								#    suggested for deprecation in Python 3.3.
							 | 
						||
| 
								 | 
							
								#  * On Python versions 2.5+ we do not need to do this as there os.stat()
							 | 
						||
| 
								 | 
							
								#    returns floating point file modification times by default.
							 | 
						||
| 
								 | 
							
								#  * Windows CPython implementations prior to version 2.5 do not support file
							 | 
						||
| 
								 | 
							
								#    modification timestamp resolutions of less than 1 second no matter whether
							 | 
						||
| 
								 | 
							
								#    these timestamps are returned as integer or floating point values.
							 | 
						||
| 
								 | 
							
								#  * Python documentation states that this should be set in a program's
							 | 
						||
| 
								 | 
							
								#    __main__ module to avoid affecting other libraries that might not be ready
							 | 
						||
| 
								 | 
							
								#    to support floating point timestamps. Since we use no such external
							 | 
						||
| 
								 | 
							
								#    libraries, we ignore this warning to make it easier to enable this feature
							 | 
						||
| 
								 | 
							
								#    in both our single & multiple-test scripts.
							 | 
						||
| 
								 | 
							
								if (2, 3) <= sys.version_info < (2, 5) and not os.stat_float_times():
							 | 
						||
| 
								 | 
							
								    os.stat_float_times(True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Quickie tests. Should use doctest instead.
							 | 
						||
| 
								 | 
							
								if __name__ == "__main__":
							 | 
						||
| 
								 | 
							
								    assert str(List("foo bar") * "/baz") == "['foo/baz', 'bar/baz']"
							 | 
						||
| 
								 | 
							
								    assert repr("foo/" * List("bar baz")) == "__main__.List('foo/bar foo/baz')"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert _contains_lines([], [])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines([], [[]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines([], [[], []])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines([], [[], [], []])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines([], [[""]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines([], [["a"]])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert _contains_lines([""], [])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(["a"], [])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(["a", "b"], [])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(["a", "b"], [[], [], []])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert _contains_lines([""], [[""]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines([""], [["a"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(["a"], [[""]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(["a", "", "b", ""], [["a"]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(["a", "", "b", ""], [[""]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(["a", "", "b"], [["b"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(["a", "b"], [[""]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(["a", "", "b", ""], [["c"]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(["a", "", "b", "x"], [["x"]])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    data = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(data, [["1", "2"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["2", "1"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["1", "3"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["1", "3"]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(data, [["1"], ["2"]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(data, [["1"], [], [], [], ["2"]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(data, [["1"], ["3"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["3"], ["1"]])
							 | 
						||
| 
								 | 
							
								    assert _contains_lines(data, [["3"], ["7"], ["8"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["1"], ["3", "5"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["1"], [""], ["5"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["1"], ["5"], ["3"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["1"], ["5", "3"]])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [[" 3"]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["3 "]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["3", ""]])
							 | 
						||
| 
								 | 
							
								    assert not _contains_lines(data, [["", "3"]])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    print("tests passed")
							 |