<commit_before>'''
Downloads the actual gm results most recently generated by the Skia buildbots,
and adds any new ones to SVN control.

Launch with --help to see more information.

Copyright 2011 Google Inc.

Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
'''

# common Python modules
import fnmatch
import optparse
import os
import re
import shutil
import sys
import tempfile

# modules declared within this same directory
import compare_baselines
import svn

USAGE_STRING = 'Usage: %s [options] [baseline_subdir]...'
HELP_STRING = '''

Downloads the actual gm results most recently generated by the Skia buildbots,
and adds any new ones to SVN control.

If no baseline_subdir is given, then this tool will download the most-recently
generated actual gm results for ALL platforms.

''' + compare_baselines.HOWTO_STRING

# Base URL of SVN repository where buildbots store actual gm image results.
SVN_BASE_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual'

OPTION_IGNORE_LOCAL_MODS = '--ignore-local-mods'
OPTION_ADD_NEW_FILES = '--add-new-files'

def GetLatestResultsSvnUrl(baseline_subdir):
    """Return SVN URL from which we can check out the MOST RECENTLY generated
    images for this baseline type.

    @param baseline_subdir indicates which platform we want images for
    """
    # trim off 'gm/' prefix
    gm_prefix = 'gm%s' % os.sep
    if not baseline_subdir.startswith(gm_prefix):
        raise Exception('baseline_subdir "%s" should start with "%s"' % (
            baseline_subdir, gm_prefix))
    return '%s/%s' % (SVN_BASE_URL, baseline_subdir[len(gm_prefix):])

def CopyMatchingFiles(source_dir, dest_dir, filename_pattern,
                      only_copy_updates=False):
    """Copy all files from source_dir that match filename_pattern, and
    save them (with their original filenames) in dest_dir.

    @param source_dir
    @param dest_dir where to save the copied files
    @param filename_pattern only copy files that match this Unix-style filename
           pattern (e.g., '*.jpg')
    @param only_copy_updates if True, only copy files that are already
           present in dest_dir
    """
    all_filenames = os.listdir(source_dir)
    matching_filenames = fnmatch.filter(all_filenames, filename_pattern)
    for filename in matching_filenames:
        source_path = os.path.join(source_dir, filename)
        dest_path = os.path.join(dest_dir, filename)
        if only_copy_updates and not os.path.isfile(dest_path):
            continue
        shutil.copyfile(source_path, dest_path)

def DownloadBaselinesForOnePlatform(baseline_subdir):
    """Download most recently generated baseline images for a single platform,
    and add any new ones to SVN control.

    @param baseline_subdir
    """
    # Create repo_to_modify to handle the SVN repository we will add files to.
    repo_to_modify = svn.Svn(baseline_subdir)

    # If there are any locally modified files in that directory, exit
    # (so that we don't risk overwriting the user's previous work).
    new_and_modified_files = repo_to_modify.GetNewAndModifiedFiles()
    if not options.ignore_local_mods:
        if new_and_modified_files:
            raise Exception('Exiting because there are already new and/or '
                            'modified files in %s.  To continue in spite of '
                            'that, run with %s option.' % (
                                baseline_subdir, OPTION_IGNORE_LOCAL_MODS))

    # Download actual gm images into a separate repo in a temporary directory.
    tempdir = tempfile.mkdtemp()
    download_repo = svn.Svn(tempdir)
    download_repo.Checkout(GetLatestResultsSvnUrl(baseline_subdir), '.')

    # Copy any of those files we are interested in into repo_to_modify,
    # and then delete the temporary directory.
    CopyMatchingFiles(source_dir=tempdir, dest_dir=baseline_subdir,
                      filename_pattern='*.png',
                      only_copy_updates=(not options.add_new_files))
    shutil.rmtree(tempdir)
    download_repo = None

    # Add any new files to SVN control (if we are running with add_new_files).
    if options.add_new_files:
        new_files = repo_to_modify.GetNewFiles()
        if new_files:
            repo_to_modify.AddFiles(sorted(new_files))

    # Set the mimetype property on any new/modified image files in
    # baseline_subdir.  (We used to set the mimetype property on *all* image
    # files in the directory, even those whose content wasn't changing,
    # but that caused confusion.  See
    # http://code.google.com/p/skia/issues/detail?id=618 .)
    modified_files = repo_to_modify.GetNewAndModifiedFiles()
    repo_to_modify.SetProperty(sorted(fnmatch.filter(modified_files, '*.png')),
                               svn.PROPERTY_MIMETYPE, 'image/png')
    repo_to_modify.SetProperty(sorted(fnmatch.filter(modified_files, '*.pdf')),
                               svn.PROPERTY_MIMETYPE, 'application/pdf')

def RaiseUsageException():
    raise Exception('%s\nRun with --help for more detail.' % (
        USAGE_STRING % __file__))

def Main(options, args):
    """Allow other scripts to call this script with fake command-line args.
    """
    # If no platforms are specified, do 'em all.
    num_args = len(args)
    if num_args == 0:
        # TODO(epoger): automate the default set of platforms. We want to ensure
        # that the user gets all of the platforms that the bots are running,
        # not just whatever subdirectories he happens to have checked out...
        args = [
            'gm/base-linux',
            'gm/base-linux-fixed',
            'gm/base-macmini',
            'gm/base-macmini-fixed',
            'gm/base-macmini-lion-fixed',
            'gm/base-macmini-lion-float',
            'gm/base-win',
            'gm/base-win-fixed',
            ]

    # Trim all subdir names.
    baseline_subdirs = []
    for arg in args:
        baseline_subdirs.append(arg.rstrip(os.sep))

    # Make sure all those subdirectories exist.
    for baseline_subdir in baseline_subdirs:
        if not os.path.isdir(baseline_subdir):
            raise Exception('could not find baseline_subdir "%s"' %
                            baseline_subdir)

    # Process the subdirs, one at a time.
    for baseline_subdir in baseline_subdirs:
        DownloadBaselinesForOnePlatform(baseline_subdir=baseline_subdir)

if __name__ == '__main__':
    parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING)
    parser.add_option(OPTION_IGNORE_LOCAL_MODS,
                      action='store_true', default=False,
                      help='allow tool to run even if there are already '
                      'local modifications in the baseline_subdir')
    parser.add_option(OPTION_ADD_NEW_FILES,
                      action='store_true', default=False,
                      help='in addition to downloading new versions of '
                      'existing baselines, also download baselines that are '
                      'not under SVN control yet')
    (options, args) = parser.parse_args()
    Main(options, args)
<commit_msg>Update download_baselines.py<commit_after>'''
Downloads the actual gm results most recently generated by the Skia buildbots,
and adds any new ones to SVN control.

Launch with --help to see more information.

Copyright 2011 Google Inc.

Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
'''

# common Python modules
import fnmatch
import optparse
import os
import re
import shutil
import sys
import tempfile

# modules declared within this same directory
import compare_baselines
import svn

USAGE_STRING = 'Usage: %s [options] [baseline_subdir]...'
HELP_STRING = '''

Downloads the actual gm results most recently generated by the Skia buildbots,
and adds any new ones to SVN control.

If no baseline_subdir is given, then this tool will download the most-recently
generated actual gm results for ALL platforms.

''' + compare_baselines.HOWTO_STRING

# Base URL of SVN repository where buildbots store actual gm image results.
SVN_BASE_URL = 'http://skia-autogen.googlecode.com/svn/gm-actual'

OPTION_IGNORE_LOCAL_MODS = '--ignore-local-mods'
OPTION_ADD_NEW_FILES = '--add-new-files'

def GetLatestResultsSvnUrl(baseline_subdir):
    """Return SVN URL from which we can check out the MOST RECENTLY generated
    images for this baseline type.

    @param baseline_subdir indicates which platform we want images for
    """
    # trim off 'gm/' prefix
    gm_prefix = 'gm%s' % os.sep
    if not baseline_subdir.startswith(gm_prefix):
        raise Exception('baseline_subdir "%s" should start with "%s"' % (
            baseline_subdir, gm_prefix))
    return '%s/%s' % (SVN_BASE_URL, baseline_subdir[len(gm_prefix):])

def CopyMatchingFiles(source_dir, dest_dir, filename_pattern,
                      only_copy_updates=False):
    """Copy all files from source_dir that match filename_pattern, and
    save them (with their original filenames) in dest_dir.

    @param source_dir
    @param dest_dir where to save the copied files
    @param filename_pattern only copy files that match this Unix-style filename
           pattern (e.g., '*.jpg')
    @param only_copy_updates if True, only copy files that are already
           present in dest_dir
    """
    all_filenames = os.listdir(source_dir)
    matching_filenames = fnmatch.filter(all_filenames, filename_pattern)
    for filename in matching_filenames:
        source_path = os.path.join(source_dir, filename)
        dest_path = os.path.join(dest_dir, filename)
        if only_copy_updates and not os.path.isfile(dest_path):
            continue
        shutil.copyfile(source_path, dest_path)

def DownloadBaselinesForOnePlatform(baseline_subdir):
    """Download most recently generated baseline images for a single platform,
    and add any new ones to SVN control.

    @param baseline_subdir
    """
    # Create repo_to_modify to handle the SVN repository we will add files to.
    repo_to_modify = svn.Svn(baseline_subdir)

    # If there are any locally modified files in that directory, exit
    # (so that we don't risk overwriting the user's previous work).
    new_and_modified_files = repo_to_modify.GetNewAndModifiedFiles()
    if not options.ignore_local_mods:
        if new_and_modified_files:
            raise Exception('Exiting because there are already new and/or '
                            'modified files in %s.  To continue in spite of '
                            'that, run with %s option.' % (
                                baseline_subdir, OPTION_IGNORE_LOCAL_MODS))

    # Download actual gm images into a separate repo in a temporary directory.
    tempdir = tempfile.mkdtemp()
    download_repo = svn.Svn(tempdir)
    download_repo.Checkout(GetLatestResultsSvnUrl(baseline_subdir), '.')

    # Copy any of those files we are interested in into repo_to_modify,
    # and then delete the temporary directory.
    CopyMatchingFiles(source_dir=tempdir, dest_dir=baseline_subdir,
                      filename_pattern='*.png',
                      only_copy_updates=(not options.add_new_files))
    shutil.rmtree(tempdir)
    download_repo = None

    # Add any new files to SVN control (if we are running with add_new_files).
    if options.add_new_files:
        new_files = repo_to_modify.GetNewFiles()
        if new_files:
            repo_to_modify.AddFiles(sorted(new_files))

    # Set the mimetype property on any new/modified image files in
    # baseline_subdir.  (We used to set the mimetype property on *all* image
    # files in the directory, even those whose content wasn't changing,
    # but that caused confusion.  See
    # http://code.google.com/p/skia/issues/detail?id=618 .)
    modified_files = repo_to_modify.GetNewAndModifiedFiles()
    repo_to_modify.SetProperty(sorted(fnmatch.filter(modified_files, '*.png')),
                               svn.PROPERTY_MIMETYPE, 'image/png')
    repo_to_modify.SetProperty(sorted(fnmatch.filter(modified_files, '*.pdf')),
                               svn.PROPERTY_MIMETYPE, 'application/pdf')

def RaiseUsageException():
    raise Exception('%s\nRun with --help for more detail.' % (
        USAGE_STRING % __file__))

def Main(options, args):
    """Allow other scripts to call this script with fake command-line args.
    """
    # If no platforms are specified, do 'em all.
    num_args = len(args)
    if num_args == 0:
        # TODO(epoger): automate the default set of platforms. We want to ensure
        # that the user gets all of the platforms that the bots are running,
        # not just whatever subdirectories he happens to have checked out...
        args = [
            'gm/base-android-nexus-s',
            'gm/base-android-xoom',
            'gm/base-linux',
            'gm/base-macmini',
            'gm/base-macmini-lion-float',
            'gm/base-win',
            ]

    # Trim all subdir names.
    baseline_subdirs = []
    for arg in args:
        baseline_subdirs.append(arg.rstrip(os.sep))

    # Make sure all those subdirectories exist.
    for baseline_subdir in baseline_subdirs:
        if not os.path.isdir(baseline_subdir):
            raise Exception('could not find baseline_subdir "%s"' %
                            baseline_subdir)

    # Process the subdirs, one at a time.
    for baseline_subdir in baseline_subdirs:
        DownloadBaselinesForOnePlatform(baseline_subdir=baseline_subdir)

if __name__ == '__main__':
    parser = optparse.OptionParser(USAGE_STRING % '%prog' + HELP_STRING)
    parser.add_option(OPTION_IGNORE_LOCAL_MODS,
                      action='store_true', default=False,
                      help='allow tool to run even if there are already '
                      'local modifications in the baseline_subdir')
    parser.add_option(OPTION_ADD_NEW_FILES,
                      action='store_true', default=False,
                      help='in addition to downloading new versions of '
                      'existing baselines, also download baselines that are '
                      'not under SVN control yet')
    (options, args) = parser.parse_args()
    Main(options, args)
<|endoftext|>


<commit_before>__version__ = '1.4'
<commit_msg>Increment version<commit_after>__version__ = '1.5'
<|endoftext|>


<commit_before>__version__ = '1.7.10'
<commit_msg>Increment version<commit_after>__version__ = '1.7.11'
<|endoftext|>


<commit_before>##########################################################################
# Copyright (c) 2009, ETH Zurich.
# All rights reserved.
#
# This file is distributed under the terms in the attached LICENSE file.
# If you do not find this file, copies can be found by writing to:
# ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
##########################################################################

import re, socket, httplib, traceback, os, subprocess, datetime, glob, time
import tests, debug, siteconfig
from common import TestCommon, TimeoutError, select_timeout
from results import ResultsBase, PassFailResult, RowResults

WEBSERVER_TEST_FILES=['index.html', 'barrelfish.gif', 'barrelfish_sosp09.pdf', 'nevill-master-capabilities.pdf', 'razavi-master-performanceisolation.pdf']

WEBSERVER_TIMEOUT=5 # seconds
TEST_LOG_NAME = 'testlog.txt'

HTTPERF_BASE_ARGS='--hog --close-with-reset --timeout 2 '
HTTPERF_URI = '/index.html'

# Webserver stress test, It will download index page repeatedly for following
#       number of times
WEBSERVER_STRESS_COUNTER = 3000

# desired duration of an httperf test run (seconds)
HTTPERF_DURATION = 20

# sleep time between runs (seconds)
HTTPERF_SLEEPTIME = 20

# timeout for a complete run, including setup etc.
HTTPERF_TIMEOUT = datetime.timedelta(seconds=(HTTPERF_DURATION + 30))

# connection rates across all client machines
HTTPERF_STARTRATE = 1000  # initial rate
HTTPERF_RATEINCREMENT = 1000  # amount to increment by for each new run

class WebCommon(TestCommon):

    def __init__(self, options):
        super(WebCommon, self).__init__(options)
        self.test_timeout_delta = datetime.timedelta(seconds=600)
        self.read_after_finished = True
        self.server_failures = []

    def setup(self, build, machine, testdir):
        super(WebCommon, self).setup(build, machine, testdir)
        self.testdir = testdir
        self.finished = False
        self.ip = None

    def get_modules(self, build, machine):
        cardName = "e1000"
        modules = super(WebCommon, self).get_modules(build, machine)
        modules.add_module("e1000n", ["auto"])
        modules.add_module("NGD_mng", ["auto"])
        modules.add_module("netd", ["auto"])
        nfsip = socket.gethostbyname(siteconfig.get('WEBSERVER_NFS_HOST'))
        modules.add_module("webserver", ["core=%d" % machine.get_coreids()[0], #2
				cardName, nfsip,
                                         siteconfig.get('WEBSERVER_NFS_PATH')])
#                                         siteconfig.get('WEBSERVER_NFS_TEST_PATH')])
        return modules

    def process_line(self, line):
        m = re.match(r'Interface up! IP address (\d+\.\d+\.\d+\.\d+)', line)
        if m:
            self.ip = m.group(1)
        elif self.ip and 'Starting webserver' in line:
            debug.verbose("Running the tests")
            self.runtests(self.ip)
            self.finished = True
        elif line.startswith("kernel PANIC!") or \
             line.startswith("Assertion failed on core") or \
             re.match("Assertion .* failed at line", line) or \
             line.startswith("Aborted"):
            # Severe error in webserver, failing test
            if line.startswith("Aborted") and \
               self.previous_line not in self.server_failures:
                line = self.previous_line
            self.server_failures.append(line.strip())
            self.finished = True

        self.previous_line = line.strip()

    def passed(self):
        return len(self.server_failures) == 0

    def is_finished(self, line):
        return self.finished

@tests.add_test
class WebserverTest(WebCommon):
    '''tests webserver functionality'''
    name = "webserver"

    def setup(self, *args):
        super(WebserverTest, self).setup(*args)
        self.testlog = None

    def getpage_stress(self, server, page, count):
        debug.verbose('requesting http://%s/%s' % (server, page))
        failure_count = 0;
        #c = httplib.HTTPConnection(server, timeout=WEBSERVER_TIMEOUT)
        for i in range(count):
            try:
                c = httplib.HTTPConnection(server, timeout=WEBSERVER_TIMEOUT)
                c.request('GET', '/' + page)
                r = c.getresponse()
                if (r.status / 100) != 2 :
                    print "HTTP request failed for %d" % (i)
                assert((r.status / 100) == 2) # check for success response

                # Reset failure count after sucessful retrival
                failure_count = 0
                c.close()
            except Exception as e:
                print "HTTP request failed for %d, (failure count %d)" % (i,
                        failure_count)
                print "Exception: ", e
                failure_count = failure_count + 1
                if failure_count >= 3:
                    print "HTTP request failed for 3 successive times."
                    print "Giving up for %d, (failure count %d)" % (i,
                        failure_count)
                    raise

            #c.close()
        debug.verbose('server replied %s %s for %d times' % (r.status, r.reason, count))

    def getpage(self, server, page):
        debug.verbose('requesting http://%s/%s' % (server, page))
        c = httplib.HTTPConnection(server, timeout=WEBSERVER_TIMEOUT)
        c.request('GET', '/' + page)
        r = c.getresponse()

        debug.verbose('server replied %s %s' % (r.status, r.reason))
        assert((r.status / 100) == 2) # check for success response

        try:
            local_path = siteconfig.get('WEBSERVER_LOCAL_PATH')
        except AttributeError:
            local_path = None
        local = os.path.join(local_path, page) if local_path else None
        if local and os.path.isfile(local) and os.access(local, os.R_OK):
            debug.verbose('comparing content to %s' % local)
            l = open(local, 'r')
            # read from both files and compare
            CHUNKSIZE = 4096
            while True:
                remote_data = r.read(CHUNKSIZE)
                local_data = l.read(CHUNKSIZE)
                if remote_data != local_data:
                    print "Remote and local data did not match:"
                    print "Remote data\n"
                    print remote_data
                    print "Local data\n"
                    print local_data
                assert(remote_data == local_data)
                if len(local_data) < CHUNKSIZE:
                    break

            debug.verbose('contents matched for %s' % local)
        c.close()

    def dotest(self, func, args):
        exception = None
        r = None
        try:
            r = func(*args)
        except Exception as e:
            exception = e

        s = 'Test: %s%s\t%s\n' % (func.__name__, str(args),
                                 'FAIL' if exception else 'PASS')
        if exception:
            debug.verbose('Exception while running test: %s\n'
                          % traceback.format_exc())
            s += 'Error was: %s\n' % traceback.format_exc()
        self.testlog.write(s)

        return r

    def runtests(self, server):
        stress_counter = WEBSERVER_STRESS_COUNTER
        self.testlog = open(os.path.join(self.testdir, TEST_LOG_NAME), 'w')
        for f in WEBSERVER_TEST_FILES:
            self.dotest(self.getpage, (server, f))
            debug.verbose("Running stresstest: (%d GET %s)" %
                    (stress_counter, str(f)))
            self.dotest(self.getpage_stress, (server, f, stress_counter))
        self.testlog.close()

    def process_data(self, testdir, rawiter):
        # the test passed iff we see at least one PASS and no FAILs in the log
        passed = None
        try:
            testlog = open(os.path.join(testdir, TEST_LOG_NAME), 'r')
        except IOError as e:
            debug.verbose("Cannot find test log, failing test")
            return PassFailResult(False, reason="Cannot find test log")

        for line in testlog:
            if re.match('Test:.*FAIL$', line):
                passed = False
            elif passed != False and re.match('Test:.*PASS$', line):
                passed = True
        testlog.close()
        server_ok = super(WebserverTest, self).passed()
        return PassFailResult(passed and server_ok)

@tests.add_test
class HTTPerfTest(WebCommon):
    '''httperf webserver performance benchmark'''
    name = "httperf"

    def setup(self, *args):
        super(HTTPerfTest, self).setup(*args)
        self.nruns = 0

    def _runtest(self, target, nclients, nconns, rate):
        self.nruns += 1
        nrun = self.nruns
        httperfs = []
        try:
            for nclient in range(nclients):
                user, host = siteconfig.site.get_load_generator()
                assert(nrun < 100 and nclient < 100)
                filename = 'httperf_run%02d_%02d.txt' % (nrun, nclient)
                logfile = open(os.path.join(self.testdir, filename), 'w')
                debug.verbose('spawning httperf on %s' % host)
                hp = HTTPerfClient(logfile, user, host, target, nconns, rate)
                httperfs.append(hp)

            # loop collecting output from all of them
            busy_httperfs = list(httperfs) # copy list
            timeout = datetime.datetime.now() + HTTPERF_TIMEOUT
            while busy_httperfs:
                (ready, _, _) = select_timeout(timeout, busy_httperfs)
                if not ready:
                    raise TimeoutError('waiting for httperfs')
                for hp in ready:
                    try:
                        hp.read()
                    except EOFError:
                        busy_httperfs.remove(hp)
        finally:
            debug.log('cleaning up httperf test...')
            for hp in httperfs:
                hp.cleanup()

    def runtests(self, target):
        nclients = siteconfig.get('HTTPERF_MAXCLIENTS')
        firstrun = True
        totalrate = HTTPERF_STARTRATE
        while True:
            if firstrun:
                firstrun = False
            else:
                # sleep a moment to let things settle down between runs
                debug.verbose('sleeping between httperf runs')
                time.sleep(HTTPERF_SLEEPTIME)

            # compute rate and total number of connections for each client
            rate = totalrate / nclients
            nconns = HTTPERF_DURATION * rate

            debug.log('starting httperf: %d clients, %d conns, rate %d (%d per client)' %
                      (nclients, nconns, totalrate, rate))
            self._runtest(target, nclients, nconns, rate)

            # decide whether to keep going...
            results = self._process_run(self.nruns)
            if not results.passed():
                debug.log('previous test failed, stopping')
                break
            elif results.request_rate < (0.9 * results.connect_rate):
                debug.log('request rate below 90% of connect rate, stopping')
                break
            elif results.reply_rate < (0.9 * results.request_rate):
                debug.log('reply rate below 90% of request rate, stopping')
                break
            else:
                totalrate += HTTPERF_RATEINCREMENT
                continue

    def _process_one(self, logfile):
        ret = HTTPerfResults()
        matches = 0

        for line in logfile:
            # Connection rate
            m = re.match('Connection rate: (\d+\.\d+) conn/s', line)
            if m:
                matches += 1
                ret.connect_rate = float(m.group(1))

            # Request rate
            m = re.match('Request rate: (\d+\.\d+) req/s', line)
            if m:
                matches += 1
                ret.request_rate = float(m.group(1))

            # Reply rate
            m = re.search('Reply rate \[replies/s\]: min .* avg (\d+\.\d+)'
                          ' max .* stddev .*', line)
            if m:
                matches += 1
                ret.reply_rate = float(m.group(1))

            # Bandwidth
            m = re.match('Net I/O: .* KB/s \((\d+\.\d+)\*10\^6 bps\)', line)
            if m:
                matches += 1
                ret.bandwidth = float(m.group(1))

            # client-side errors
            m = re.match('Errors: fd-unavail (\d+) addrunavail (\d+)'
                         ' ftab-full (\d+) other (\d+)', line)
            if m:
                matches += 1
                ret.fd_unavail = int(m.group(1))
                ret.addrunavail = int(m.group(2))
                ret.ftab_full = int(m.group(3))
                ret.other_err = int(m.group(4))

            # server-side errors
            m = re.match('Errors: total \d+ client-timo (\d+) socket-timo (\d+)'
                         ' connrefused (\d+) connreset (\d+)', line)
            if m:
                matches += 1
                ret.client_timo = int(m.group(1))
                ret.socket_timo = int(m.group(2))
                ret.connrefused = int(m.group(3))
                ret.connreset = int(m.group(4))

        if matches != 6 : # otherwise we have an invalid log
            print "Instead of 6, only %d matches found\n" % (matches)

        return ret

    def _process_run(self, nrun):
        nameglob = 'httperf_run%02d_*.txt' % nrun
        results = []
        for filename in glob.iglob(os.path.join(self.testdir, nameglob)):
            with open(filename, 'r') as logfile:
                results.append(self._process_one(logfile))
        return sum(results, HTTPerfResults())

    def process_data(self, testdir, raw_iter):
        self.testdir = testdir
        totals = {}
        for filename in glob.iglob(os.path.join(testdir, 'httperf_run*.txt')):
            nrun = int(re.match('.*/httperf_run(\d+)_', filename).group(1))
            result = self._process_run(nrun)
            totals[nrun] = result

        fields = 'run connect_rate request_rate reply_rate bandwidth errors'.split()
        final = RowResults(fields)

        for run in sorted(totals.keys()):
            total = totals[run]
            errsum = sum([getattr(total, f) for f in total._err_fields])
            final.add_row([run, total.connect_rate, total.request_rate,
                           total.reply_rate, total.bandwidth, errsum])
            # XXX: often the last run will have errors in it, due to the control algorithm
            #if errsum:
            #    final.mark_failed()

        # If we saw a severe failure (assertion failure, kernel panic, or user
        # level panic) in the webserver, fail the test
        if not super(HTTPerfTest, self).passed():
            final.mark_failed('\n'.join(self.server_failures))

        return final

class HTTPerfResults(ResultsBase):
    _err_fields = 'fd_unavail addrunavail ftab_full other_err'.split()
    _result_fields = ('client_timo socket_timo connrefused connreset'
                      ' connect_rate request_rate bandwidth reply_rate').split()
    _fields = _err_fields + _result_fields

    def __init__(self):
        super(HTTPerfResults, self).__init__()
        for f in self._fields:
            setattr(self, f, 0)

    def __add__(self, other):
        ret = HTTPerfResults()
        for f in self._fields:
            setattr(ret, f, getattr(self, f) + getattr(other, f))
        return ret

    def passed(self):
        return all([getattr(self, field) == 0 for field in self._err_fields])

    def to_file(self, fh):
        errs = [(f, getattr(self,f)) for f in self._err_fields if getattr(self,f)]
        if errs:
            fh.write('Failed run: ' + ' '.join(['%s %d' % e for e in errs]))

        fh.write('Request rate:\t%f\n' % self.request_rate)
        fh.write('Bandwidth:\t%f\n' % self.bandwidth)
        fh.write('Reply rate:\t%f\n' % self.reply_rate)

class HTTPerfClient(object):
    def __init__(self, logfile, user, host, target, nconns, rate):
        self.user = user
        self.host = host
        self.httperf_path = siteconfig.get('HTTPERF_PATH')
        cmd = '%s %s' % (self.httperf_path, HTTPERF_BASE_ARGS)
        cmd += ' --num-conns %d --rate %d --server %s --uri %s' % (
                        nconns, rate, target, HTTPERF_URI)
        self.proc = self._launchssh(cmd, stdout=subprocess.PIPE, bufsize=0)
        self.logfile = logfile

    def _launchssh(self, remotecmd, **kwargs):
        ssh_dest = '%s@%s' % (self.user, self.host)
        cmd = ['ssh'] + siteconfig.get('SSH_ARGS').split() + [ssh_dest, remotecmd]
        return subprocess.Popen(cmd, **kwargs)

    # mirror builtin file method so that we can pass this to select()
    def fileno(self):
        return self.proc.stdout.fileno()

    def read(self):
        # read only a single character to avoid blocking!
        s = self.proc.stdout.read(1)
        if s == '':
            raise EOFError
        self.logfile.write(s)

    def cleanup(self):
        """perform cleanup if necessary"""
        self.logfile.close()
        if self.proc is None or self.proc.poll() == 0:
            return # clean exit

        if self.proc.returncode:
            debug.warning('httperf: SSH to %s exited with error %d'
                          % (self.host, self.proc.returncode))
        else: # kill SSH if still up
            debug.warning('httperf: killing SSH child for %s' % self.host)
            self.proc.terminate()
            self.proc.wait()

        # run a remote killall to get rid of any errant httperfs
        debug.verbose('killing any errant httperfs on %s' % self.host)
        p = self._launchssh('killall -q %s' % self.httperf_path)
        retcode = p.wait()
        if retcode != 0:
            debug.warning('failed to killall httperf on %s!' % self.host)
<commit_msg>harness: Delete tests/tftp.py which is copied tests/webserver.py<commit_after><|endoftext|>


<commit_before>#
# Description:
#   This module implements helper functions
#   used to perform pseudo-interactive monitoring
#
# Prerequisites:
#   The startd must be configured with exactly 2 slots, called vm
#   It must have cross-vm expressions State and RemoteUser enabled.
#   It also must advertize that has the monitor vm.
#
# Author:
#   Igor Sfiligoi (May 2007)
#

import string
import time
import tempfile
import shutil
import os.path
import sys

# This should be done by the user of the module
#sys.path.append("../../lib")

import condorMonitor
import condorManager

# returns a dictionary of jid,schedd_name,pool_name, timeout and argv
# the argv contains the arguments not parsed by the function
def parseArgs(argv):
    outdict={'schedd_name':None,'pool_name':None,
             'timeout':30} #default
    jid=None
    alen=len(argv)
    i=0
    while i<alen:
        ael=argv[i]
        if ael=='-name':
            i=i+1
            outdict['schedd_name']=argv[i]
        elif ael=='-pool':
            i=i+1
            outdict['pool_name']=argv[i]
        elif ael=='-timeout':
            i=i+1
            outdict['timeout']=int(argv[i])
        else:
            if jid==None:
                jid=ael
            else:
                # first unknown element
                # return the rest to the caller
                break
        i=i+1

    if jid==None:
        raise RuntimeError, 'JID not found'
    outdict['jid']=jid
    outdict['argv']=argv[i:]
    return outdict

def monitor(jid,schedd_name,pool_name,
            timeout,
            createMonitorFile,argv):
    try:
        jid_cluster,jid_proc=string.split(jid,".",1)
    except:
        raise RuntimeError, 'Invalid JID %s, expected Cluster.Proc'%jid

    constraint="(ClusterId=?=%s) && (ProcId=?=%s)"%(jid_cluster,jid_proc)

    remoteVM=getRemoteVM(pool_name,schedd_name,constraint)
    monitorVM=getMonitorVM(remoteVM)

    condor_status=getCondorStatus(pool_name,remoteVM,monitorVM)
    validateCondorStatus(condor_status,remoteVM,monitorVM)

    tmpdir=tempfile.mkdtemp(prefix="glidein_intmon_")
    try:
        sname=os.path.join(tmpdir,"mon.submit")
        mfname=os.path.join(tmpdir,"mon.sh")
        mfout=os.path.join(tmpdir,"mon.out")
        mferr=os.path.join(tmpdir,"mon.err")
        mlog=os.path.join(tmpdir,"mon.log")
        mc_relname="mon.done"
        mcname=os.path.join(tmpdir,mc_relname)
        createMonitorFile(mfname,mc_relname,argv,condor_status[remoteVM])
        createSubmitFile(tmpdir,sname,mlog,mfname,mfout,mferr,
                         monitorVM,timeout)
        jid=condorManager.condorSubmitOne(sname,schedd_name,pool_name)
        try:
            checkFile(mcname,timeout)
            printFile(mfout,sys.stdout)
            printFile(mferr,sys.stderr)
        except:
            condorManager.condorRemoveOne(jid,schedd_name,pool_name)
            raise
    finally:
        shutil.rmtree(tmpdir)
    return

######## Internal ############

def getRemoteVM(pool_name,schedd_name,constraint):
    cq=condorMonitor.CondorQ(schedd_name=schedd_name,pool_name=pool_name)
    data=cq.fetch(constraint)
    if len(data.keys())==0:
        raise RuntimeError, "Job not found"
    if len(data.keys())>1:
        raise RuntimeError, "Can handle only one job at a time"
    el=data.values()[0]
    if (not el.has_key('JobStatus')) or (el['JobStatus']!=2):
        raise RuntimeError, "Job not running"
    if not el.has_key('RemoteHost'):
        raise RuntimeError, "Job still starting"

    return el['RemoteHost']

def getMonitorVM(remoteVM):
    rvm_arr=string.split(remoteVM,"@",1)
    if len(rvm_arr)!=2:
        raise RuntimeError, "Not running on a multi-VM resource"
    vm,host=rvm_arr
    if vm!="vm2":
        raise RuntimeError, "Not running on a supported glidein (%s!=vm2)"%vm
    return "%s@%s"%("vm1",host)

def getCondorStatus(pool_name,jobVM,monitorVM):
    cs=condorMonitor.CondorStatus(pool_name=pool_name)
    data=cs.fetch(constraint='(Name=="%s") || (Name=="%s")'%(jobVM,monitorVM))
    return data

def validateCondorStatus(condor_status,jobVM,monitorVM):
    if not condor_status.has_key(jobVM):
        raise RuntimeError, "Job claims it runs on %s, but cannot find it!"%jobVM
    if not condor_status.has_key(monitorVM):
        raise RuntimeError, "No monitoring VM found (should be %s)"%monitorVM

    if ((not condor_status[monitorVM].has_key('HAS_MONITOR_VM')) or
        (condor_status[monitorVM]['HAS_MONITOR_VM']!=True)):
        raise RuntimeError, "The startd does not allow monitoring"

    if condor_status[monitorVM]['State']=='Claimed':
        raise RuntimeError, "Job cannot be monitored right now"

    if ((not condor_status[monitorVM].has_key('vm2_State')) or
        (condor_status[monitorVM]['vm2_State']!='Claimed')):
        raise RuntimeError, "Job cannot be yet monitored"
    return

def createSubmitFile(work_dir,sfile,mlog,
                     mfname,mfout,mferr,
                     monitorVM,timeout):
    fd=open(sfile,"w")
    try:
        fd.write("universe=vanilla\n")
        fd.write("executable=%s\n"%mfname)
        fd.write("initialdir=%s\n"%work_dir)
        fd.write("output=%s\n"%mfout)
        fd.write("error=%s\n"%mferr)
        fd.write("log=%s\n"%mlog)
        fd.write("transfer_executable=True\n")
        fd.write("when_to_transfer_output=ON_EXIT\n")
        fd.write("notification=Never\n")
        fd.write("+GLIDEIN_Is_Monitor=True\n")
        fd.write('Requirements=(Name=?="%s")&&(Arch=!="Absurd")\n'%monitorVM)
        fd.write("periodic_remove=(CurrentTime>%li)\n"%(long(time.time())+timeout+30)) # karakiri after timeout+delta
        fd.write("queue\n")
    finally:
        fd.close()

def checkFile(fname,timeout):
    deadline=time.time()+timeout
    while (time.time()<deadline):
        if os.path.exists(fname):
            return True
        time.sleep(1)
    raise RuntimeError, "Command did not reply within timeout (%ss)"%timeout

def printFile(fname,outfd):
    fd=open(fname)
    try:
        data=fd.read()
        outfd.write(data)
    finally:
        fd.close()

<commit_msg>Add Owner=Undefined; needed when using ono-FS auth<commit_after>#
# Description:
#   This module implements helper functions
#   used to perform pseudo-interactive monitoring
#
# Prerequisites:
#   The startd must be configured with exactly 2 slots, called vm
#   It must have cross-vm expressions State and RemoteUser enabled.
#   It also must advertize that has the monitor vm.
#
# Author:
#   Igor Sfiligoi (May 2007)
#

import string
import time
import tempfile
import shutil
import os.path
import sys

# This should be done by the user of the module
#sys.path.append("../../lib")

import condorMonitor
import condorManager

# returns a dictionary of jid,schedd_name,pool_name, timeout and argv
# the argv contains the arguments not parsed by the function
def parseArgs(argv):
    outdict={'schedd_name':None,'pool_name':None,
             'timeout':30} #default
    jid=None
    alen=len(argv)
    i=0
    while i<alen:
        ael=argv[i]
        if ael=='-name':
            i=i+1
            outdict['schedd_name']=argv[i]
        elif ael=='-pool':
            i=i+1
            outdict['pool_name']=argv[i]
        elif ael=='-timeout':
            i=i+1
            outdict['timeout']=int(argv[i])
        else:
            if jid==None:
                jid=ael
            else:
                # first unknown element
                # return the rest to the caller
                break
        i=i+1

    if jid==None:
        raise RuntimeError, 'JID not found'
    outdict['jid']=jid
    outdict['argv']=argv[i:]
    return outdict

def monitor(jid,schedd_name,pool_name,
            timeout,
            createMonitorFile,argv):
    try:
        jid_cluster,jid_proc=string.split(jid,".",1)
    except:
        raise RuntimeError, 'Invalid JID %s, expected Cluster.Proc'%jid

    constraint="(ClusterId=?=%s) && (ProcId=?=%s)"%(jid_cluster,jid_proc)

    remoteVM=getRemoteVM(pool_name,schedd_name,constraint)
    monitorVM=getMonitorVM(remoteVM)

    condor_status=getCondorStatus(pool_name,remoteVM,monitorVM)
    validateCondorStatus(condor_status,remoteVM,monitorVM)

    tmpdir=tempfile.mkdtemp(prefix="glidein_intmon_")
    try:
        sname=os.path.join(tmpdir,"mon.submit")
        mfname=os.path.join(tmpdir,"mon.sh")
        mfout=os.path.join(tmpdir,"mon.out")
        mferr=os.path.join(tmpdir,"mon.err")
        mlog=os.path.join(tmpdir,"mon.log")
        mc_relname="mon.done"
        mcname=os.path.join(tmpdir,mc_relname)
        createMonitorFile(mfname,mc_relname,argv,condor_status[remoteVM])
        createSubmitFile(tmpdir,sname,mlog,mfname,mfout,mferr,
                         monitorVM,timeout)
        jid=condorManager.condorSubmitOne(sname,schedd_name,pool_name)
        try:
            checkFile(mcname,timeout)
            printFile(mfout,sys.stdout)
            printFile(mferr,sys.stderr)
        except:
            condorManager.condorRemoveOne(jid,schedd_name,pool_name)
            raise
    finally:
        shutil.rmtree(tmpdir)
    return

######## Internal ############

def getRemoteVM(pool_name,schedd_name,constraint):
    cq=condorMonitor.CondorQ(schedd_name=schedd_name,pool_name=pool_name)
    data=cq.fetch(constraint)
    if len(data.keys())==0:
        raise RuntimeError, "Job not found"
    if len(data.keys())>1:
        raise RuntimeError, "Can handle only one job at a time"
    el=data.values()[0]
    if (not el.has_key('JobStatus')) or (el['JobStatus']!=2):
        raise RuntimeError, "Job not running"
    if not el.has_key('RemoteHost'):
        raise RuntimeError, "Job still starting"

    return el['RemoteHost']

def getMonitorVM(remoteVM):
    rvm_arr=string.split(remoteVM,"@",1)
    if len(rvm_arr)!=2:
        raise RuntimeError, "Not running on a multi-VM resource"
    vm,host=rvm_arr
    if vm!="vm2":
        raise RuntimeError, "Not running on a supported glidein (%s!=vm2)"%vm
    return "%s@%s"%("vm1",host)

def getCondorStatus(pool_name,jobVM,monitorVM):
    cs=condorMonitor.CondorStatus(pool_name=pool_name)
    data=cs.fetch(constraint='(Name=="%s") || (Name=="%s")'%(jobVM,monitorVM))
    return data

def validateCondorStatus(condor_status,jobVM,monitorVM):
    if not condor_status.has_key(jobVM):
        raise RuntimeError, "Job claims it runs on %s, but cannot find it!"%jobVM
    if not condor_status.has_key(monitorVM):
        raise RuntimeError, "No monitoring VM found (should be %s)"%monitorVM

    if ((not condor_status[monitorVM].has_key('HAS_MONITOR_VM')) or
        (condor_status[monitorVM]['HAS_MONITOR_VM']!=True)):
        raise RuntimeError, "The startd does not allow monitoring"

    if condor_status[monitorVM]['State']=='Claimed':
        raise RuntimeError, "Job cannot be monitored right now"

    if ((not condor_status[monitorVM].has_key('vm2_State')) or
        (condor_status[monitorVM]['vm2_State']!='Claimed')):
        raise RuntimeError, "Job cannot be yet monitored"
    return

def createSubmitFile(work_dir,sfile,mlog,
                     mfname,mfout,mferr,
                     monitorVM,timeout):
    fd=open(sfile,"w")
    try:
        fd.write("universe=vanilla\n")
        fd.write("executable=%s\n"%mfname)
        fd.write("initialdir=%s\n"%work_dir)
        fd.write("output=%s\n"%mfout)
        fd.write("error=%s\n"%mferr)
        fd.write("log=%s\n"%mlog)
        fd.write("transfer_executable=True\n")
        fd.write("when_to_transfer_output=ON_EXIT\n")
        fd.write("notification=Never\n")
        fd.write("+GLIDEIN_Is_Monitor=True\n")
        fd.write("+Owner=Undefined\n")
        fd.write('Requirements=(Name=?="%s")&&(Arch=!="Absurd")\n'%monitorVM)
        fd.write("periodic_remove=(CurrentTime>%li)\n"%(long(time.time())+timeout+30)) # karakiri after timeout+delta
        fd.write("queue\n")
    finally:
        fd.close()

def checkFile(fname,timeout):
    deadline=time.time()+timeout
    while (time.time()<deadline):
        if os.path.exists(fname):
            return True
        time.sleep(1)
    raise RuntimeError, "Command did not reply within timeout (%ss)"%timeout

def printFile(fname,outfd):
    fd=open(fname)
    try:
        data=fd.read()
        outfd.write(data)
    finally:
        fd.close()

<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import atexit
import os
import sys
from datetime import datetime

from pyparsing import ParseException
from yalix.exceptions import YalixError
from yalix.completer import Completer
from yalix.interpreter.primitives import *
from yalix.interpreter.builtins import *
from yalix.parser import parse
from yalix.utils import log_progress, log
from yalix.utils.color import red, green, blue, bold
from yalix.globals import create_initial_env

def version():
    return '0.0.1'

def copyright():
    return Atom("""
Copyright (c) {0} Richard Hull.
All Rights Reserved.""".format(datetime.now().year))

def license():
    return Atom("""
The MIT License (MIT)

Copyright (c) {0} Richard Hull

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.""".format(datetime.now().year))

def help():
    return Atom("""
Yalix is a LISP interpreter: it's dialect most closely resembles that of Racket
or PLT-Scheme. This particular implementation is written in Python, and serves
as a reference implementation only. It is subject to on-going development, and
as such will suffer breaking changes, as well as being buggy and not having
been optimised. It is therefore not recommended for production use.

The main purpose of this software is to provide a pedagogical experience in
writing a LISP interpreter and a core library of functions, in a number of
different computer languages. The intention is that there will eventually be
"Seven LISPs in seven languages", where each implementation is idiomatic in
the target language. Although not fully decided, the languages will most
likely be: Python, Lua, Julia, Java/Scala, Haskell, a.n.other, and course
there really needs to be a Yalix implementation too!

The REPL that you are now connected to has readline capabilities, and stores
a history in ~/.yalix-history. Pressing <TAB> on a partially typed word will
interrogate the environment for matching definitions and will present a word
completion facility.

Documentation for a particular function (e.g. first) can be found by evaluating
the following S-Exp at the prompt:

    (doc first)

See https://github.com/rm-hull/yalex/ for further information.""")

def credits():
    return Atom("""
TBD""")

def init_readline(env):
    try:
        import readline
    except ImportError:
        print "Module readline not available"
    else:
        histfile = os.path.join(os.path.expanduser("~"), ".yalix_history")
        readline.set_completer(Completer(env).complete)
        readline.parse_and_bind("tab: complete")
        if hasattr(readline, "read_history_file"):
            try:
                with log_progress("Reading history"):
                    readline.read_history_file(histfile)

            except IOError:
                pass
            atexit.register(readline.write_history_file, histfile)

def balance(text, bal=0):
    """
    Checks whether the parens in the text are balanced:
        - zero: balanced
        - negative: too many right parens
        - positive: too many left parens
    """
    if text == '':
        return bal
    elif text[0] == '(' and bal >= 0:
        return balance(text[1:], bal + 1)
    elif text[0] == ')':
        return balance(text[1:], bal - 1)
    else:
        return balance(text[1:], bal)

def read(count, primary_prompt):
    prompt = primary_prompt.format(count)
    secondary_prompt = ' ' * len(str(count)) + green('  ...: ')

    entry = ''
    while True:
        entry += raw_input(prompt) + '\n'
        if balance(entry) == 0:
            return entry
        prompt = secondary_prompt + '  ' * balance(entry)

def prn(input, result, count, prompt):
    print prompt.format(count, result)

def ready():
    log()
    log(bold('Yalix [{0}]') + ' on Python {1} {2}', version(), sys.version, sys.platform)
    log('Type "help", "copyright", "credits" or "license" for more information.')

def repl(print_callback=prn):
    env = create_initial_env()
    env['copyright'] = copyright()
    env['license'] = license()
    env['help'] = help()
    env['credits'] = credits()

    init_readline(env)
    ready()

    in_prompt = green('In [') + green(bold('{0}')) + green(']: ')
    out_prompt = red('Out[') + red(bold('{0}')) + red(']: ') + '{1}\n'

    count = 1
    while True:
        try:
            text = read(count, in_prompt)
            for ast in parse(text):
                result = ast.eval(env)
                print_callback(text, result, count, out_prompt)

        except EOFError:
            log(bold(blue('\nBye!')))
            break

        except KeyboardInterrupt:
            log(bold(red('\nKeyboardInterrupt')))

        except (YalixError, ParseException) as ex:
            log("{0}: {1}", bold(red(type(ex).__name__)), ex)
        count += 1

env = create_initial_env()

lst1 = Cons(Atom(4), Cons(Atom(2), Cons(Atom(3), Nil())))
lst2 = List(Atom(4), Atom(2), Atom(3))

# Eq(Cons(Atom(4), Cons(Atom(2), Cons(Atom(3), Nil()))), lst1).eval(env)
# Eq(lst2, lst1).eval(env)
#
# Eq(Atom(True),
#   Not(Atom(False))).eval(env)

Nil_QUESTION(Nil()).eval(env)
Nil_QUESTION(Atom('Freddy')).eval(env)

Atom_QUESTION(Atom('Freddy')).eval(env)
Atom_QUESTION(Nil()).eval(env)

# Eq(Nil(), Atom(None)).eval(env)

# Car(Cdr(lst1)).eval(env)

(Cdr(Nil())).eval(env)

Let("f",
    Atom("Hello"),
    Cons(Symbol("f"),
         Symbol("f"))).eval(env)

Let_STAR([('a', Atom('Hello')),
          ('b', List(Atom(1), Atom(2), Atom(3))),
          ('c', Atom('World')),
          ('c', List(Atom('Big'), Symbol('c')))],  # <-- re-def shadowing
         List(Symbol('a'), Symbol('c'), Symbol('b'))).eval(env)

Let('identity',
    Lambda(['x'], Symbol('x')),  # <-- anonymous fn
    Call(Symbol('identity'), Atom(99))).eval(env)

# InterOp, i.e. using Python functions
import operator
InterOp(operator.add, Atom(41), Atom(23)).eval(env)

# check unicode, pi & golden ratio
Define('π', Atom(3.14159265358979323846264338327950288419716939937510)).eval(env)
Define('ϕ', Atom(1.618033988749894848204586834)).eval(env)
env['π']
env['ϕ']

Symbol('π').eval(env)
Symbol('ϕ').eval(env)

# from __future__ import print_function
# Define('print', Lambda(['text'], InterOp(print_function, Symbol('text')))).eval(env)

Call(Symbol('+'), Atom(99), Atom(55)).eval(env)
Call([Symbol('+'), Atom(99), Atom(55)]).eval(env)
Call(array_to_linked_list([Symbol('+'), Atom(99), Atom(55)])).eval(env)
array_to_linked_list([Symbol('+'), Atom(99), Atom(55)])

env['+']

Call(Symbol('random')).eval(env)

q = Quote([Symbol('+'), Atom(2), Atom(3)]).eval(env)
q
Call(q).eval(env)

# Call(Quote([Symbol('+'), Atom(2), Atom(3)])).eval(env)

# (let (rnd (random))
#   (if (< rnd 0.5)
#     "Unlucky"
#     (if (< rnd 0.75)
#       "Close, but no cigar"
#       "Lucky")))
#
Let('rnd',
    Call(Symbol('random')),
    If(Call(Symbol('<'), Symbol('rnd'), Atom(0.5)),
       Atom("Unlucky"),
       If(Call(Symbol('<'), Symbol('rnd'), Atom(0.75)),
          Atom("Close, but no cigar"),
          Atom("Lucky")))).eval(env)

# (define factorial
#   (lambda (x)
#     (if (zero? x)
#       1
#       (* x (factorial (- x 1))))))
#
Define('zero?', Lambda(['n'], Call(Symbol('='), Symbol('n'), Atom(0)))).eval(env)
Define('factorial', Lambda(['x'],
                           If(Call(Symbol('zero?'), Symbol('x')),
                              Atom(1),
                              Call(Symbol('*'),
                                   Symbol('x'),
                                   Call(Symbol('factorial'),
                                        Call(Symbol('-'),
                                             Symbol('x'),
                                             Atom(1))))))).eval(env)

# (factorial 10)
Call(Symbol('factorial'), Atom(10)).eval(env)

# (format "{0} PI = {1}" (list 2 (* 2 math/pi)))
Call(Symbol('format'),
     Atom('{0} PI = {1}'),
     List(Atom(2),
          Call(Symbol('*'),
               Atom(2),
               Symbol('math/pi')))).eval(env)

# (gensym)
g = Call(Symbol('gensym')).eval(env)
g
g.name

# =============================================================================================

test2 = """
(define *hello* ["world" -1 #t 3.14159 [1 2 3]])

(let (rnd (random))
  (if (< rnd 0.5)
    "Unlucky"
    (if (< rnd 0.75)
      "Close, but no cigar"
      "Lucky")))

(define factorial
  (lambda (x)
    (if (zero? x)
      1
      (* x (factorial (- x 1))))))

(factorial 10)

*hello*

"""

# No yet fully supported
# (let* ((x 5)
#        (y (+ x 7))
#        (z (+ y x 2)))
#    (* x y z))

for ptree in parse(test2):
    print ptree.eval(env)
<commit_msg>Prefill REPL input with 2-char indents for subsequent lines<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import atexit
import os
import sys
from datetime import datetime

from pyparsing import ParseException
from yalix.exceptions import YalixError
from yalix.completer import Completer
from yalix.interpreter.primitives import *
from yalix.interpreter.builtins import *
from yalix.parser import parse
from yalix.utils import log_progress, log
from yalix.utils.color import red, green, blue, bold
from yalix.globals import create_initial_env

def version():
    return '0.0.1'

def copyright():
    return Atom("""
Copyright (c) {0} Richard Hull.
All Rights Reserved.""".format(datetime.now().year))

def license():
    return Atom("""
The MIT License (MIT)

Copyright (c) {0} Richard Hull

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.""".format(datetime.now().year))

def help():
    return Atom("""
Yalix is a LISP interpreter: it's dialect most closely resembles that of Racket
or PLT-Scheme. This particular implementation is written in Python, and serves
as a reference implementation only. It is subject to on-going development, and
as such will suffer breaking changes, as well as being buggy and not having
been optimised. It is therefore not recommended for production use.

The main purpose of this software is to provide a pedagogical experience in
writing a LISP interpreter and a core library of functions, in a number of
different computer languages. The intention is that there will eventually be
"Seven LISPs in seven languages", where each implementation is idiomatic in
the target language. Although not fully decided, the languages will most
likely be: Python, Lua, Julia, Java/Scala, Haskell, a.n.other, and course
there really needs to be a Yalix implementation too!

The REPL that you are now connected to has readline capabilities, and stores
a history in ~/.yalix-history. Pressing <TAB> on a partially typed word will
interrogate the environment for matching definitions and will present a word
completion facility.

Documentation for a particular function (e.g. first) can be found by evaluating
the following S-Exp at the prompt:

    (doc first)

See https://github.com/rm-hull/yalex/ for further information.""")

def credits():
    return Atom("""
TBD""")

def init_readline(env):
    try:
        import readline
    except ImportError:
        print "Module readline not available"
    else:
        histfile = os.path.join(os.path.expanduser("~"), ".yalix_history")
        readline.set_completer(Completer(env).complete)
        readline.parse_and_bind("tab: complete")
        if hasattr(readline, "read_history_file"):
            try:
                with log_progress("Reading history"):
                    readline.read_history_file(histfile)

            except IOError:
                pass
            atexit.register(readline.write_history_file, histfile)

def input_with_prefill(prompt, text):
    try:
        import readline
        def hook():
            readline.insert_text(text)
            readline.redisplay()
        readline.set_pre_input_hook(hook)
        return raw_input(prompt)
    except ImportError:
        return raw_input(prompt)
    finally:
        readline.set_pre_input_hook()

def balance(text, bal=0):
    """
    Checks whether the parens in the text are balanced:
        - zero: balanced
        - negative: too many right parens
        - positive: too many left parens
    """
    if text == '':
        return bal
    elif text[0] == '(' and bal >= 0:
        return balance(text[1:], bal + 1)
    elif text[0] == ')':
        return balance(text[1:], bal - 1)
    else:
        return balance(text[1:], bal)

def read(count, primary_prompt):
    prompt = primary_prompt.format(count)
    secondary_prompt = ' ' * len(str(count)) + green('  ...: ')

    prefill = ''
    entry = ''

    while True:
        entry += input_with_prefill(prompt, prefill) + '\n'
        prompt = secondary_prompt
        parens_count = balance(entry)
        if parens_count == 0:
            return entry
        elif parens_count > 0:
            prefill = '  ' * parens_count

def prn(input, result, count, prompt):
    print prompt.format(count, result)

def ready():
    log()
    log(bold('Yalix [{0}]') + ' on Python {1} {2}', version(), sys.version, sys.platform)
    log('Type "help", "copyright", "credits" or "license" for more information.')

def repl(print_callback=prn):
    env = create_initial_env()
    env['copyright'] = copyright()
    env['license'] = license()
    env['help'] = help()
    env['credits'] = credits()

    init_readline(env)
    ready()

    in_prompt = green('In [') + green(bold('{0}')) + green(']: ')
    out_prompt = red('Out[') + red(bold('{0}')) + red(']: ') + '{1}\n'

    count = 1
    while True:
        try:
            text = read(count, in_prompt)
            for ast in parse(text):
                result = ast.eval(env)
                print_callback(text, result, count, out_prompt)

        except EOFError:
            log(bold(blue('\nBye!')))
            break

        except KeyboardInterrupt:
            log(bold(red('\nKeyboardInterrupt')))

        except (YalixError, ParseException) as ex:
            log("{0}: {1}", bold(red(type(ex).__name__)), ex)
        count += 1

env = create_initial_env()

lst1 = Cons(Atom(4), Cons(Atom(2), Cons(Atom(3), Nil())))
lst2 = List(Atom(4), Atom(2), Atom(3))

# Eq(Cons(Atom(4), Cons(Atom(2), Cons(Atom(3), Nil()))), lst1).eval(env)
# Eq(lst2, lst1).eval(env)
#
# Eq(Atom(True),
#   Not(Atom(False))).eval(env)

Nil_QUESTION(Nil()).eval(env)
Nil_QUESTION(Atom('Freddy')).eval(env)

Atom_QUESTION(Atom('Freddy')).eval(env)
Atom_QUESTION(Nil()).eval(env)

# Eq(Nil(), Atom(None)).eval(env)

# Car(Cdr(lst1)).eval(env)

(Cdr(Nil())).eval(env)

Let("f",
    Atom("Hello"),
    Cons(Symbol("f"),
         Symbol("f"))).eval(env)

Let_STAR([('a', Atom('Hello')),
          ('b', List(Atom(1), Atom(2), Atom(3))),
          ('c', Atom('World')),
          ('c', List(Atom('Big'), Symbol('c')))],  # <-- re-def shadowing
         List(Symbol('a'), Symbol('c'), Symbol('b'))).eval(env)

Let('identity',
    Lambda(['x'], Symbol('x')),  # <-- anonymous fn
    Call(Symbol('identity'), Atom(99))).eval(env)

# InterOp, i.e. using Python functions
import operator
InterOp(operator.add, Atom(41), Atom(23)).eval(env)

# check unicode, pi & golden ratio
Define('π', Atom(3.14159265358979323846264338327950288419716939937510)).eval(env)
Define('ϕ', Atom(1.618033988749894848204586834)).eval(env)
env['π']
env['ϕ']

Symbol('π').eval(env)
Symbol('ϕ').eval(env)

# from __future__ import print_function
# Define('print', Lambda(['text'], InterOp(print_function, Symbol('text')))).eval(env)

Call(Symbol('+'), Atom(99), Atom(55)).eval(env)
Call([Symbol('+'), Atom(99), Atom(55)]).eval(env)
Call(array_to_linked_list([Symbol('+'), Atom(99), Atom(55)])).eval(env)
array_to_linked_list([Symbol('+'), Atom(99), Atom(55)])

env['+']

Call(Symbol('random')).eval(env)

q = Quote([Symbol('+'), Atom(2), Atom(3)]).eval(env)
q
Call(q).eval(env)

# Call(Quote([Symbol('+'), Atom(2), Atom(3)])).eval(env)

# (let (rnd (random))
#   (if (< rnd 0.5)
#     "Unlucky"
#     (if (< rnd 0.75)
#       "Close, but no cigar"
#       "Lucky")))
#
Let('rnd',
    Call(Symbol('random')),
    If(Call(Symbol('<'), Symbol('rnd'), Atom(0.5)),
       Atom("Unlucky"),
       If(Call(Symbol('<'), Symbol('rnd'), Atom(0.75)),
          Atom("Close, but no cigar"),
          Atom("Lucky")))).eval(env)

# (define factorial
#   (lambda (x)
#     (if (zero? x)
#       1
#       (* x (factorial (- x 1))))))
#
Define('zero?', Lambda(['n'], Call(Symbol('='), Symbol('n'), Atom(0)))).eval(env)
Define('factorial', Lambda(['x'],
                           If(Call(Symbol('zero?'), Symbol('x')),
                              Atom(1),
                              Call(Symbol('*'),
                                   Symbol('x'),
                                   Call(Symbol('factorial'),
                                        Call(Symbol('-'),
                                             Symbol('x'),
                                             Atom(1))))))).eval(env)

# (factorial 10)
Call(Symbol('factorial'), Atom(10)).eval(env)

# (format "{0} PI = {1}" (list 2 (* 2 math/pi)))
Call(Symbol('format'),
     Atom('{0} PI = {1}'),
     List(Atom(2),
          Call(Symbol('*'),
               Atom(2),
               Symbol('math/pi')))).eval(env)

# (gensym)
g = Call(Symbol('gensym')).eval(env)
g
g.name

# =============================================================================================

test2 = """
(define *hello* ["world" -1 #t 3.14159 [1 2 3]])

(let (rnd (random))
  (if (< rnd 0.5)
    "Unlucky"
    (if (< rnd 0.75)
      "Close, but no cigar"
      "Lucky")))

(define factorial
  (lambda (x)
    (if (zero? x)
      1
      (* x (factorial (- x 1))))))

(factorial 10)

*hello*

"""

# No yet fully supported
# (let* ((x 5)
#        (y (+ x 7))
#        (z (+ y x 2)))
#    (* x y z))

for ptree in parse(test2):
    print ptree.eval(env)
<|endoftext|>


<commit_before>#!/bin/python

# Copyright (c) 2013, Arvid Norberg
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in
#       the documentation and/or other materials provided with the distribution.
#     * Neither the name of the author nor the names of its
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# This is meant to be run from the root directory of the repo. It will
# look for the .regression.yml file and expect a regression_tests directory
# with results from test runs previously produced by run_tests.py

import os
import sys
import glob
import json
import yaml

# TODO: different parsers could be run on output from different actions
# if we would use the xml output in stead of stdout/stderr
def style_output(o):
	ret = ''
	subtle = False
	for l in o.split('\n'):
		if 'TEST_CHECK' in l or 'TEST_EQUAL_ERROR' in l or l.startswith('EXIT STATUS: ') or \
			l.endswith(' second time limit exceeded'):
			ret += '%s\n' % l
		elif '**passed**' in l:
			ret += '%s\n' % l
		elif ': error: ' in l or ': fatal error: ' in l or ' : fatal error ' in l or \
			'failed to write output file' in l or ') : error C' in l or \
			') : error LNK' in l or ': undefined reference to ' in l:
			ret += '%s\n' % l
		elif ': warning: ' in l or ') : warning C' in l:
			ret += '%s\n' % l
		elif l == '====== END OUTPUT ======' and not subtle:
			ret += '%s\n' % l
			subtle = True
		else:
			ret += '%s\n' % l
	if subtle: ret += ''
	return ret

def modification_time(file):
	mtime = 0
	try:
		mtime = os.stat(file).st_mtime
	except: pass
	return mtime

def save_log_file(log_name, project_name, branch_name, test_name, timestamp, data):

	if not os.path.exists(os.path.split(log_name)[0]):
		os.mkdir(os.path.split(log_name)[0])

	try:
		# if the log file already exists, and it's newer than
		# the source, no need to re-parse it
		mtime = os.stat(log_name).st_mtime
		if mtime >= timestamp: return
	except: pass

	html = open(log_name, 'w+')
	print >>html, '''<title>%s - %s</title>

%s - %s
''' % (project_name, branch_name, project_name, branch_name)
	print >>html, '
%s

%s
' % \
		(test_name.encode('utf-8'), style_output(data).encode('utf-8'))

	print >>html, ''
	html.close()
	sys.stdout.write('.')
	sys.stdout.flush()

def parse_tests(rev_dir):

	# this contains mappings from platforms to
	# the next layer of dictionaries. The next
	# layer contains a mapping of toolsets to
	# dictionaries the next layer of dictionaries.
	# those dictionaries contain a mapping from
	# feature-sets to the next layer of dictionaries.
	# the next layer contains a mapping from
	# tests to information about those tests, such
	# as whether it passed and the output from the
	# command
	# example:

	# {
	#   darwin: {
	#     clang-4.2.1: {
	#       ipv6=off: {
	#         test_primitives: {
	#           output: ...
	#           status: 1
	#         }
	#       }
	#     }
	#   }
	# }

	platforms = {}

	tests = {}

	for f in glob.glob(os.path.join(rev_dir, '*.json')):
		platform_toolset = os.path.split(f)[1].split('.json')[0].split('#')
		try:
			j = json.loads(open(f, 'rb').read())
			timestamp = os.stat(f).st_mtime
		except:
			print '\nFAILED TO LOAD "%s"\n' %f
			continue

		platform = platform_toolset[0]
		toolset = platform_toolset[1]

		for cfg in j:
			test_name = cfg.split('|')[0]
			features = cfg.split('|')[1]

			if not features in tests:
				tests[features] = set()

			tests[features].add(test_name)

			if not platform in platforms:
				platforms[platform] = {}

			if not toolset in platforms[platform]:
				platforms[platform][toolset] = {}

			if not features in platforms[platform][toolset]:
				platforms[platform][toolset][features] = {}

			platforms[platform][toolset][features][test_name] = j[cfg]
			platforms[platform][toolset][features][test_name]['timestamp'] = timestamp

	return (platforms, tests)

# TODO: remove this dependency by encoding it in the output files
# this script should work from outside of the repo, just having
# access to the shared folder
project_name = ''

try:
	cfg = open('.regression.yml', 'r')
except:
	print '.regression.yml not found in current directory'
	sys.exit(1)
cfg = yaml.load(cfg.read())

if 'project' in cfg:
	project_name = cfg['project']

branch_name = 'trunk'
if 'branch' in cfg:
	branch_name = cfg['branch'].strip()

print 'branch: %s' % branch_name

os.chdir('regression_tests')

if len(sys.argv) > 1:
	latest_rev = int(sys.argv[1])
else:
	latest_rev = 0

	for rev in os.listdir('.'):
		try:
			if not rev.startswith('%s-' % branch_name): continue
			r = int(rev[len(branch_name)+1:])
			if r > latest_rev: latest_rev = r
		except: pass

	if latest_rev == 0:
		print 'no test files found'
		sys.exit(1)

print 'latest version: %d' % latest_rev

html_file = 'index.html'

'''
html_file = '%s.html' % rev_dir
index_mtime = modification_time(html_file)

need_refresh = False

for f in glob.glob(os.path.join(rev_dir, '*.json')):
	mtime = modification_time(f)

	if mtime > index_mtime:
		need_refresh = True
		break

if not need_refresh:
	print 'all up to date'
	sys.exit(0)
'''

html = open(html_file, 'w+')

print >>html, '''<title>regression tests, %s</title>

%s - %s
''' % (project_name, project_name, branch_name)

print >>html, '
'

for r in range(latest_rev, latest_rev - 20, -1):
	sys.stdout.write('.')
	sys.stdout.flush()

	print >>html, '
revision %d	' % r

	rev_dir = '%s-%d' % (branch_name, r)
	(platforms, tests) = parse_tests(rev_dir)

	for f in tests:
		print >>html, '%s	' % (len(tests[f]), len(tests[f])*9 - 5, f)
	print >>html, '
'

	for p in platforms:
		print >>html, '
%s	' % (len(platforms[p]), p)
		idx = 0
		for toolset in platforms[p]:
			if idx > 0: print >>html, '
'
			print >>html, '%s	' % toolset
			for f in platforms[p][toolset]:
				for t in platforms[p][toolset][f]:
					details = platforms[p][toolset][f][t]
					if details['status'] == 0: c = 'passed'
					else: c = 'failed'
					log_name = os.path.join('logs-%s-%d' % (branch_name, r), p + '~' + toolset + '~' + t + '~' + f.replace(' ', '.') + '.html')
					print >>html, '	' % (t, f, c, log_name)
					save_log_file(log_name, project_name, branch_name, '%s - %s' % (t, f), int(details['timestamp']), details['output'])

			print >>html, '
'
			idx += 1

print >>html, '
'
html.close()

print ''

<commit_msg>improve test result html<commit_after>#!/bin/python

# Copyright (c) 2013, Arvid Norberg
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in
#       the documentation and/or other materials provided with the distribution.
#     * Neither the name of the author nor the names of its
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# This is meant to be run from the root directory of the repo. It will
# look for the .regression.yml file and expect a regression_tests directory
# with results from test runs previously produced by run_tests.py

import os
import sys
import glob
import json
import yaml

# TODO: different parsers could be run on output from different actions
# if we would use the xml output in stead of stdout/stderr
def style_output(o):
	ret = ''
	subtle = False
	for l in o.split('\n'):
		if 'TEST_CHECK' in l or 'TEST_EQUAL_ERROR' in l or l.startswith('EXIT STATUS: ') or \
			l.endswith(' second time limit exceeded'):
			ret += '%s\n' % l
		elif '**passed**' in l:
			ret += '%s\n' % l
		elif ': error: ' in l or ': fatal error: ' in l or ' : fatal error ' in l or \
			'failed to write output file' in l or ') : error C' in l or \
			') : error LNK' in l or ': undefined reference to ' in l:
			ret += '%s\n' % l
		elif ': warning: ' in l or ') : warning C' in l:
			ret += '%s\n' % l
		elif l == '====== END OUTPUT ======' and not subtle:
			ret += '%s\n' % l
			subtle = True
		else:
			ret += '%s\n' % l
	if subtle: ret += ''
	return ret

def modification_time(file):
	mtime = 0
	try:
		mtime = os.stat(file).st_mtime
	except: pass
	return mtime

def save_log_file(log_name, project_name, branch_name, test_name, timestamp, data):

	if not os.path.exists(os.path.split(log_name)[0]):
		os.mkdir(os.path.split(log_name)[0])

	try:
		# if the log file already exists, and it's newer than
		# the source, no need to re-parse it
		mtime = os.stat(log_name).st_mtime
		if mtime >= timestamp: return
	except: pass

	html = open(log_name, 'w+')
	print >>html, '''<title>%s - %s</title>

%s - %s
''' % (project_name, branch_name, project_name, branch_name)
	print >>html, '
%s

%s
' % \
		(test_name.encode('utf-8'), style_output(data).encode('utf-8'))

	print >>html, ''
	html.close()
	sys.stdout.write('.')
	sys.stdout.flush()

def parse_tests(rev_dir):

	# this contains mappings from platforms to
	# the next layer of dictionaries. The next
	# layer contains a mapping of toolsets to
	# dictionaries the next layer of dictionaries.
	# those dictionaries contain a mapping from
	# feature-sets to the next layer of dictionaries.
	# the next layer contains a mapping from
	# tests to information about those tests, such
	# as whether it passed and the output from the
	# command
	# example:

	# {
	#   darwin: {
	#     clang-4.2.1: {
	#       ipv6=off: {
	#         test_primitives: {
	#           output: ...
	#           status: 1
	#         }
	#       }
	#     }
	#   }
	# }

	platforms = {}

	tests = {}

	for f in glob.glob(os.path.join(rev_dir, '*.json')):
		platform_toolset = os.path.split(f)[1].split('.json')[0].split('#')
		try:
			j = json.loads(open(f, 'rb').read())
			timestamp = os.stat(f).st_mtime
		except:
			print '\nFAILED TO LOAD "%s"\n' %f
			continue

		platform = platform_toolset[0]
		toolset = platform_toolset[1]

		for cfg in j:
			test_name = cfg.split('|')[0]
			features = cfg.split('|')[1]

			if not features in tests:
				tests[features] = set()

			tests[features].add(test_name)

			if not platform in platforms:
				platforms[platform] = {}

			if not toolset in platforms[platform]:
				platforms[platform][toolset] = {}

			if not features in platforms[platform][toolset]:
				platforms[platform][toolset][features] = {}

			platforms[platform][toolset][features][test_name] = j[cfg]
			platforms[platform][toolset][features][test_name]['timestamp'] = timestamp

	return (platforms, tests)

# TODO: remove this dependency by encoding it in the output files
# this script should work from outside of the repo, just having
# access to the shared folder
project_name = ''

try:
	cfg = open('.regression.yml', 'r')
except:
	print '.regression.yml not found in current directory'
	sys.exit(1)
cfg = yaml.load(cfg.read())

if 'project' in cfg:
	project_name = cfg['project']

branch_name = 'trunk'
if 'branch' in cfg:
	branch_name = cfg['branch'].strip()

print 'branch: %s' % branch_name

os.chdir('regression_tests')

if len(sys.argv) > 1:
	latest_rev = int(sys.argv[1])
else:
	latest_rev = 0

	for rev in os.listdir('.'):
		try:
			if not rev.startswith('%s-' % branch_name): continue
			r = int(rev[len(branch_name)+1:])
			if r > latest_rev: latest_rev = r
		except: pass

	if latest_rev == 0:
		print 'no test files found'
		sys.exit(1)

print 'latest version: %d' % latest_rev

html_file = 'index.html'

'''
html_file = '%s.html' % rev_dir
index_mtime = modification_time(html_file)

need_refresh = False

for f in glob.glob(os.path.join(rev_dir, '*.json')):
	mtime = modification_time(f)

	if mtime > index_mtime:
		need_refresh = True
		break

if not need_refresh:
	print 'all up to date'
	sys.exit(0)
'''

html = open(html_file, 'w+')

print >>html, '''<title>regression tests, %s</title>

%s - %s
''' % (project_name, project_name, branch_name)

print >>html, '
'

for r in range(latest_rev, latest_rev - 20, -1):
	sys.stdout.write('.')
	sys.stdout.flush()

	print >>html, '
revision %d	' % r

	rev_dir = '%s-%d' % (branch_name, r)
	(platforms, tests) = parse_tests(rev_dir)

	for f in tests:
		print >>html, '%s	' % (len(tests[f]), len(tests[f])*9 - 5, f)
	print >>html, '
'

	for p in platforms:
		print >>html, '
%s	' % (len(platforms[p]), p)
		idx = 0
		for toolset in platforms[p]:
			if idx > 0: print >>html, '
'
			print >>html, '%s	' % toolset
			for f in platforms[p][toolset]:
				for t in platforms[p][toolset][f]:
					details = platforms[p][toolset][f][t]
					if details['status'] == 0: c = 'passed'
					else: c = 'failed'
					log_name = os.path.join('logs-%s-%d' % (branch_name, r), p + '~' + toolset + '~' + t + '~' + f.replace(' ', '.') + '.html')
					print >>html, '	' % (t, f, c, log_name)
					save_log_file(log_name, project_name, branch_name, '%s - %s' % (t, f), int(details['timestamp']), details['output'])

			print >>html, '
'
			idx += 1

print >>html, '
'
html.close()

print ''

<|endoftext|>