Пример #1
0
def pull_from_trac(sage_root, ticket_id, branch=None, force=None,
                   use_ccache=False,
                   safe_only=False):
    """
    Create four branches from base and ticket.

    If ticket deemed unsafe then clone git repo to temp directory. ?!

    Additionally, if ``use_ccache`` then install ccache. Set some global
    and environment variables.

    There are four branches at play here:

    - patchbot/base -- the latest release that all tickets are merged into
      for testing
    - patchbot/base_upstream -- temporary staging area for patchbot/base
    - patchbot/ticket_upstream -- pristine clone of the ticket on trac
    - patchbot/ticket_merged -- merge of patchbot/ticket_upstream into
      patchbot/base
    """
    merge_failure = False
    is_safe = False
    try:
        os.chdir(sage_root)
        info = scrape(ticket_id)
        ensure_free_space(sage_root)
        do_or_die("git checkout patchbot/base")
        if ticket_id == 0:
            do_or_die("git branch -f patchbot/ticket_upstream patchbot/base")
            do_or_die("git branch -f patchbot/ticket_merged patchbot/base")
            return
        branch = info['git_branch']
        repo = info['git_repo']
        do_or_die("git fetch %s +%s:patchbot/ticket_upstream" % (repo, branch))
        base = describe_branch('patchbot/ticket_upstream', tag_only=True)
        do_or_die("git rev-list --left-right --count %s..patchbot/ticket_upstream" % base)
        do_or_die("git branch -f patchbot/ticket_merged patchbot/base")
        do_or_die("git checkout patchbot/ticket_merged")
        try:
            do_or_die("git merge -X patience patchbot/ticket_upstream")
        except Exception:
            do_or_die("git merge --abort")
            merge_failure = True
            raise
        is_safe = inplace_safe()
        if not is_safe:
            if safe_only:
                raise SkipTicket("unsafe")
            tmp_dir = tempfile.mkdtemp(temp_build_suffix + str(ticket_id))
            ensure_free_space(tmp_dir)
            do_or_die("git clone . '{}'".format(tmp_dir))
            os.chdir(tmp_dir)
            os.symlink(os.path.join(sage_root, "upstream"), "upstream")
            os.environ['SAGE_ROOT'] = tmp_dir
            do_or_die("git branch -f patchbot/base remotes/origin/patchbot/base")
            do_or_die("git branch -f patchbot/ticket_upstream remotes/origin/patchbot/ticket_upstream")
            if use_ccache:
                if not os.path.exists('logs'):
                    os.mkdir('logs')
                do_or_die("./sage -i ccache")
    except Exception as exn:
        if merge_failure or (not is_safe):
            raise
        else:
            raise ConfigException(exn.message)
Пример #2
0
def test_a_ticket(sage_root, server, ticket=None, nodocs=False):
    base = get_base(sage_root)
    if ticket is None:
        ticket = get_ticket(base=base, server=server, **conf)
    else:
        ticket = None, scrape(int(ticket))
    if not ticket:
        print "No more tickets."
        if random.random() < 0.01:
            cleanup(sage_root, server)
        time.sleep(conf['idle'])
        return
    rating, ticket = ticket
    print "\n" * 2
    print "=" * 30, ticket['id'], "=" * 30
    print ticket['title']
    print "score", rating
    print "\n" * 2
    log_dir = sage_root + "/logs"
    if not os.path.exists(log_dir):
        os.mkdir(log_dir)
    log = '%s/%s-log.txt' % (log_dir, ticket['id'])
    report_ticket(server, ticket, status='Pending', base=base, machine=conf['machine'], user=conf['user'], log=None)
    plugins_results = []
    try:
        with Tee(log, time=True, timeout=conf['timeout']):
            t = Timer()
            start_time = time.time()

            state = 'started'
            os.environ['MAKE'] = "make -j%s" % conf['parallelism']
            os.environ['SAGE_ROOT'] = sage_root
            # TODO: Ensure that sage-main is pristine.
            pull_from_trac(sage_root, ticket['id'], force=True)
            t.finish("Apply")
            state = 'applied'
            
            do_or_die('$SAGE_ROOT/sage -b %s' % ticket['id'])
            t.finish("Build")
            state = 'built'
            
            working_dir = "%s/devel/sage-%s" % (sage_root, ticket['id'])
            # Only the ones on this ticket.
            patches = os.popen2('hg --cwd %s qapplied' % working_dir)[1].read().strip().split('\n')[-len(ticket['patches']):]
            kwds = {
                "original_dir": "%s/devel/sage-0" % sage_root,
                "patched_dir": working_dir,
                "patches": ["%s/devel/sage-%s/.hg/patches/%s" % (sage_root, ticket['id'], p) for p in patches if p],
            }
            for name, plugin in conf['plugins']:
                try:
                    print plugin_boundary(name)
                    plugin(ticket, **kwds)
                    passed = True
                except Exception:
                    traceback.print_exc()
                    passed = False
                finally:
                    t.finish(name)
                    print plugin_boundary(name, end=True)
                    plugins_results.append((name, passed))
                    
            test_dirs = ["$SAGE_ROOT/devel/sage-%s/%s" % (ticket['id'], dir) for dir in all_test_dirs]
            if conf['parallelism'] > 1:
                test_cmd = "-tp %s" % conf['parallelism']
            else:
                test_cmd = "-t"
            do_or_die("$SAGE_ROOT/sage %s -sagenb %s" % (test_cmd, ' '.join(test_dirs)))
            #do_or_die("$SAGE_ROOT/sage -t $SAGE_ROOT/devel/sage-%s/sage/rings/integer.pyx" % ticket['id'])
            #do_or_die('sage -testall')
            t.finish("Tests")
            state = 'tested'
            
            if not all(passed for name, passed in plugins_results):
                state = 'failed_plugin'

            print
            t.print_all()
    except Exception:
        traceback.print_exc()
    
    for _ in range(5):
        try:
            print "Reporting", ticket['id'], status[state]
            report_ticket(server, ticket, status=status[state], base=base, machine=conf['machine'], user=conf['user'], log=log, plugins=plugins_results)
            print "Done reporting", ticket['id']
            break
        except urllib2.HTTPError:
            traceback.print_exc()
            time.sleep(conf['idle'])
    else:
        print "Error reporting", ticket['id']
    return status[state]
Пример #3
0
                if not force:
                    raise Exception, "Duplicate patch: %s" % patch
                old_patch = patch
                while old_patch in series:
                    old_patch += '-old'
                do_or_die('hg qrename %s %s' % (patch, old_patch))
            try:
                do_or_die('hg qimport %s' % url)
            except Exception, exn:
                time.sleep(30)
                try:
                    do_or_die('hg qimport %s' % url)
                except Exception, exn:
                    raise urllib2.HTTPError(exn)
            do_or_die('hg qpush')
        do_or_die('hg qapplied')
    except:
        os.system('hg qpop -a')
        raise


def push_from_trac(sage_root, ticket, branch=None, force=None, interactive=None):
    raise NotImplementedError



if __name__ == '__main__':
    force = False
    apply = False
    for ticket in sys.argv[1:]:
        if ticket == '-f':
Пример #4
0
def pull_from_trac(sage_root, ticket, branch=None, force=None, interactive=None):
    # Should we set/unset SAGE_ROOT and SAGE_BRANCH here? Fork first?
    if branch is None:
        branch = str(ticket)
    if not os.path.exists('%s/devel/sage-%s' % (sage_root, branch)):
        do_or_die('%s/sage -b main' % (sage_root,))
        do_or_die('%s/sage -clone %s' % (sage_root, branch))
    os.chdir('%s/devel/sage-%s' % (sage_root, branch))
    if interactive:
        raise NotImplementedError
    if not os.path.exists('.hg/patches'):
        do_or_die('hg qinit')
        series = []
    elif not os.path.exists('.hg/patches/series'):
        series = []
    else:
        series = open('.hg/patches/series').read().split('\n')

    base = get_base(sage_root)
    desired_series = []
    seen_deps = []
    def append_patch_list(ticket, dependency=False):
        if ticket in seen_deps:
            return
        print "Looking at #%s" % ticket
        seen_deps.append(ticket)
        data = scrape(ticket)
        if dependency and 'closed' in data['status']:
            merged = data.get('merged')
            if merged is None:
                merged = data.get('milestone')
            if merged is None or compare_version(merged, base) <= 0:
                print "#%s already applied (%s <= %s)" % (ticket, merged, base)
                return
        if data['spkgs']:
            raise NotImplementedError, "Spkgs not yet handled."
        if data['depends_on']:
            for dep in data['depends_on']:
                if isinstance(dep, basestring) and '.' in dep:
                    if compare_version(base, dep) < 0:
                        raise ValueError, "%s < %s for %s" % (base, dep, ticket)
                    continue
                append_patch_list(dep, dependency=True)
        print "Patches for #%s:" % ticket
        print "    " + "\n    ".join(data['patches'])
        for patch in data['patches']:
            patchfile, hash = patch.split('#')
            desired_series.append((hash, patchfile, get_patch_url(ticket, patchfile)))
    append_patch_list(ticket)
    
    ensure_safe(series)
    ensure_safe(patch for hash, patch, url in desired_series)

    last_good_patch = '-a'
    to_push = list(desired_series)
    for series_patch, (hash, patch, url) in zip(series, desired_series):
        if not series_patch:
            break
        next_hash = digest(open('.hg/patches/%s' % series_patch).read())
#        print next_hash, hash, series_patch
        if next_hash == hash:
            to_push.pop(0)
            last_good_patch = series_patch
        else:
            break

    try:
        if last_good_patch != '-a':
            # In case it's not yet pushed...
            if last_good_patch not in os.popen2('hg qapplied')[1].read().split('\n'):
                do_or_die('hg qpush %s' % last_good_patch)
        do_or_die('hg qpop %s' % last_good_patch)
        for hash, patch, url in to_push:
            if patch in series:
                if not force:
                    raise Exception, "Duplicate patch: %s" % patch
                old_patch = patch
                while old_patch in series:
                    old_patch += '-old'
                do_or_die('hg qrename %s %s' % (patch, old_patch))
            try:
                do_or_die('hg qimport %s' % url)
            except Exception, exn:
                time.sleep(30)
                try:
                    do_or_die('hg qimport %s' % url)
                except Exception, exn:
                    raise urllib2.HTTPError(exn)
            do_or_die('hg qpush')
Пример #5
0
    def check_spkg(self, spkg):
        temp_dir = None
        try:
            if '#' in spkg:
                spkg = spkg.split('#')[0]
            basename = os.path.basename(spkg)
            temp_dir = tempfile.mkdtemp()
            local_spkg = os.path.join(temp_dir, basename)
            do_or_die("wget --progress=dot:mega -O %s %s" % (local_spkg, spkg))
            do_or_die("tar xf %s -C %s" % (local_spkg, temp_dir))

            print
            print "Sha1", basename, sha1file(local_spkg)
            print
            print "Checking repo status."
            do_or_die("cd %s; hg diff; echo $?" % local_spkg[:-5])
            print
            print
            print "Comparing to previous spkg."

            # Compare to the current version.
            base = basename.split('-')[0] # the reset is the version
            old_path = old_url = listing = None
            if False:
                # There seems to be a bug...
                #  File "/data/sage/sage-5.5/local/lib/python2.7/site-packages/pexpect.py", line 1137, in which
                #      if os.access (filename, os.X_OK) and not os.path.isdir(f):

                import pexpect
                p = pexpect.spawn("%s/sage" % self.sage_root,  ['-i', '--info', base])
                while True:
                    index = p.expect([
                            r"Found package %s in (\S+)" % base,
                            r">>> Checking online list of (\S+) packages.",
                            r">>> Found (%s-\S+)" % base,
                            r"Error: could not find a package"])
                    if index == 0:
                        old_path = "$SAGE_ROOT/" + p.match.group(1)
                        break
                    elif index == 1:
                        listing = p.match.group(2)
                    elif index == 2:
                        old_url = "http://www.sagemath.org/packages/%s/%s.spkg" % (listing, p.match.group(1))
                        break
                    else:
                        print "No previous match."
                        break
            else:
                p = subprocess.Popen(r"%s/sage -i --info %s" % (self.sage_root, base),
                                     shell=True, stdout=subprocess.PIPE)
                for line in p.communicate()[0].split('\n'):
                    m = re.match(r"Found package %s in (\S+)" % base, line)
                    if m:
                        old_path = os.path.join(self.sage_root, m.group(1))
                        break
                    m = re.match(r">>> Checking online list of (\S+) packages.", line)
                    if m:
                        listing = m.group(1)
                    m = re.match(r">>> Found (%s-\S+)" % base, line)
                    if m:
                        old_url = "http://www.sagemath.org/packages/%s/%s.spkg" % (listing, m.group(1))
                        break
                if not old_path and not old_url:
                    print "Unable to locate existing package %s." % base

            if old_path is not None and old_path.startswith('/attachment/'):
                old_url = 'http://trac.sagemath.org/sage_trac' + old_path
            if old_url is not None:
                old_basename = os.path.basename(old_url)
                old_path = os.path.join(temp_dir, old_basename)
                if not os.path.exists(old_path):
                    do_or_die("wget --progress=dot:mega %s -O %s" % (old_url, old_path))
            if old_path is not None:
                old_basename = os.path.basename(old_path)
                if old_basename == basename:
                    print "PACKAGE NOT RENAMED"
                else:
                    do_or_die("tar xf %s -C %s" % (old_path, temp_dir))
                    print '\n\n', '-' * 20
                    do_or_die("diff -N -u -r -x src -x .hg %s/%s %s/%s; echo $?" % (temp_dir, old_basename[:-5], temp_dir, basename[:-5]))
                    print '\n\n', '-' * 20
                    do_or_die("diff -q -r %s/%s/src %s/%s/src; echo $?" % (temp_dir, old_basename[:-5], temp_dir, basename[:-5]))

            print
            print "-" * 20
            if old_path:
                do_or_die("head -n 100 %s/SPKG.txt" % local_spkg[:-5])
            else:
                do_or_die("cat %s/SPKG.txt" % local_spkg[:-5])


        finally:
            if temp_dir and os.path.exists(temp_dir):
                shutil.rmtree(temp_dir)
Пример #6
0
def main(args):
    global conf

    # Most configuration is done in the config file, which is reread between
    # each ticket for live configuration of the patchbot.
    parser = OptionParser()
    parser.add_option("--config", dest="config")
    parser.add_option("--sage-root", dest="sage_root", default=os.environ.get('SAGE_ROOT'))
    parser.add_option("--server", dest="server", default="http://patchbot.sagemath.org/")
    parser.add_option("--count", dest="count", default=1000000)
    parser.add_option("--ticket", dest="ticket", default=None)
    parser.add_option("--list", dest="list", default=False)
    parser.add_option("--full", action="store_true", dest="full", default=False)
    parser.add_option("--skip-base", action="store_true", dest="skip_base", default=False)
    parser.add_option("--dry-run", action="store_true", dest="dry_run", default=False)
    parser.add_option("--plugin-only", action="store_true", dest="plugin_only", default=False)
    (options, args) = parser.parse_args(args)

    conf_path = options.config and os.path.abspath(options.config)
    if options.ticket:
        tickets = [int(t) for t in options.ticket.split(',')]
        count = len(tickets)
    else:
        tickets = None
        count = int(options.count)

    patchbot = Patchbot(os.path.abspath(options.sage_root), options.server, conf_path, dry_run=options.dry_run, plugin_only=options.plugin_only)

    conf = patchbot.get_config()
    if options.list:
        count = sys.maxint if options.list is "True" else int(options.list)
        print "Getting ticket list..."
        for ix, (score, ticket) in enumerate(patchbot.get_ticket_list()):
            if ix >= count:
                break
            print score, '\t', ticket['id'], '\t', ticket['title']
            if options.full:
                print ticket
                print
        sys.exit(0)

    if options.sage_root == os.environ.get('SAGE_ROOT'):
        print "WARNING: Do not use this copy of sage while the patchbot is running."

    if conf['use_ccache']:
        do_or_die("'%s'/sage -i ccache" % options.sage_root)
        # If we rebuild the (same) compiler we still want to share the cache.
        os.environ['CCACHE_COMPILERCHECK'] = '%compiler% --version'

    if not options.skip_base:
        patchbot.check_base()
        def good(report):
            return report['machine'] == conf['machine'] and report['status'] == 'TestsPassed'
        if options.plugin_only or not any(good(report) for report in patchbot.current_reports(0)):
            res = patchbot.test_a_ticket(0)
            if res not in  ('TestsPassed', 'PluginOnly'):
                print "\n\n"
                while True:
                    print "Failing tests in your install: %s. Continue anyways? [y/N] " % res
                    ans = sys.stdin.readline().lower().strip()
                    if ans == '' or ans[0] == 'n':
                        sys.exit(1)
                    elif ans[0] == 'y':
                        break

    for k in range(count):
        try:
            if tickets:
                ticket = tickets.pop(0)
            else:
                ticket = None
            conf = patchbot.reload_config()
            if check_time_of_day(conf['time_of_day']):
                if not patchbot.check_base():
                    patchbot.test_a_ticket(0)
                patchbot.test_a_ticket(ticket)
            else:
                print "Idle."
                time.sleep(conf['idle'])
        except Exception:
            traceback.print_exc()
            time.sleep(conf['idle'])
Пример #7
0
    def test_a_ticket(self, ticket=None):

        self.reload_config()

        if ticket is None:
            ticket = self.get_ticket()
        else:
            ticket = None, scrape(int(ticket))
        if not ticket:
            print "No more tickets."
            time.sleep(self.config['idle'])
            return

        rating, ticket = ticket
        print "\n" * 2
        print "=" * 30, ticket['id'], "=" * 30
        print ticket['title']
        print "score", rating
        print "\n" * 2
        self.log_dir = self.sage_root + "/logs/patchbot"
        if not os.path.exists(self.log_dir):
            os.makedirs(self.log_dir)
        log = '%s/%s-log.txt' % (self.log_dir, ticket['id'])
        history = open("%s/history.txt" % self.log_dir, "a")
        history.write("%s %s\n" % (datetime(), ticket['id']))
        history.close()
        if not self.plugin_only:
            self.report_ticket(ticket, status='Pending', log=log)
        plugins_results = []
        try:
            t = Timer()
            with Tee(log, time=True, timeout=self.config['timeout'], timer=t):
                print "Sage Patchbot", patchbot_version.get_version()

                if ticket['spkgs']:
                    state = 'spkg'
                    print "\n".join(ticket['spkgs'])
                    print
                    for spkg in ticket['spkgs']:
                        print
                        print '+' * 10, spkg, '+' * 10
                        print
                        try:
                            self.check_spkg(spkg)
                        except Exception:
                            traceback.print_exc()
                        t.finish(spkg)

                if not ticket['spkgs']:
                    state = 'started'
                    os.environ['MAKE'] = "make -j%s" % self.config['parallelism']
                    os.environ['SAGE_ROOT'] = self.sage_root
                    os.environ['GIT_AUTHOR_NAME'] = os.environ['GIT_COMMITTER_NAME'] = 'patchbot'
                    os.environ['GIT_AUTHOR_EMAIL'] = os.environ['GIT_COMMITTER_EMAIL'] = 'patchbot@localhost'
                    os.environ['GIT_AUTHOR_DATE'] = os.environ['GIT_COMMITTER_DATE'] = '1970-01-01T00:00:00'
                    pull_from_trac(self.sage_root, ticket['id'], force=True, use_ccache=self.config['use_ccache'])
                    t.finish("Apply")
                    state = 'applied'
                    if not self.plugin_only:
                        self.report_ticket(ticket, status='Pending', log=log, pending_status=state)

                    do_or_die("$MAKE")
                    t.finish("Build")
                    state = 'built'
                    if not self.plugin_only:
                        self.report_ticket(ticket, status='Pending', log=log, pending_status=state)

                    # TODO: Exclude dependencies.
                    patch_dir = tempfile.mkdtemp()
                    if ticket['id'] != 0:
                        do_or_die("git format-patch -o '%s' patchbot/base..patchbot/ticket_merged" % patch_dir)

                    kwds = {
                        "original_dir": self.sage_root,
                        "patched_dir": os.getcwd(),
                        "patches": [os.path.join(patch_dir, p) for p in os.listdir(patch_dir)],
                        "sage_binary": os.path.join(os.getcwd(), 'sage'),
                        "dry_run": self.dry_run,
                    }

                    for name, plugin in self.config['plugins']:
                        try:
                            if ticket['id'] != 0 and os.path.exists(os.path.join(self.log_dir, '0', name)):
                                baseline = pickle.load(open(os.path.join(self.log_dir, '0', name)))
                            else:
                                baseline = None
                            print plugin_boundary(name)
                            do_or_die("git checkout patchbot/ticket_merged")
                            res = plugin(ticket, is_git=True, baseline=baseline, **kwds)
                            passed = True
                        except Exception:
                            traceback.print_exc()
                            passed = False
                            res = None
                        finally:
                            if isinstance(res, PluginResult):
                                if res.baseline is not None:
                                    plugin_dir = os.path.join(self.log_dir, str(ticket['id']))
                                    if not os.path.exists(plugin_dir):
                                        os.mkdir(plugin_dir)
                                    pickle.dump(res.baseline, open(os.path.join(plugin_dir, name), 'w'))
                                    passed = res.status == PluginResult.Passed
                                    print name, res.status
                                    plugins_results.append((name, passed, res.data))
                            else:
                                plugins_results.append((name, passed, None))
                            t.finish(name)
                            print plugin_boundary(name, end=True)
                    plugins_passed = all(passed for (name, passed, data) in plugins_results)
                    self.report_ticket(ticket, status='Pending', log=log, pending_status='plugins_passed' if plugins_passed else 'plugins_failed')

                    if self.plugin_only:
                        state = 'plugins' if plugins_passed else 'plugins_failed'
                    else:
                        if self.dry_run:
                            test_target = "$SAGE_ROOT/src/sage/misc/a*.py"
                            # TODO: Remove
                            test_target = "$SAGE_ROOT/src/sage/doctest/*.py"
                        else:
                            test_target = "--all --long"
                        if self.config['parallelism'] > 1:
                            test_cmd = "-tp %s" % self.config['parallelism']
                        else:
                            test_cmd = "-t"
                        do_or_die("$SAGE_ROOT/sage %s %s" % (test_cmd, test_target))
                        t.finish("Tests")
                        state = 'tested'

                        if not plugins_passed:
                            state = 'tests_passed_plugins_failed'

        except (urllib2.HTTPError, socket.error):
            # Don't report failure because the network/trac died...
            print
            t.print_all()
            traceback.print_exc()
            state = 'network_error'
        except Exception:
            traceback.print_exc()

        for _ in range(5):
            try:
                print "Reporting", ticket['id'], status[state]
                self.report_ticket(ticket, status=status[state], log=log, plugins=plugins_results, dry_run=self.dry_run)
                print "Done reporting", ticket['id']
                break
            except IOError:
                traceback.print_exc()
                time.sleep(self.config['idle'])
        else:
            print "Error reporting", ticket['id']
        maybe_temp_root = os.environ['SAGE_ROOT']
        if maybe_temp_root.endswith("-sage-git-temp-%s" % ticket['id']):
            shutil.rmtree(maybe_temp_root)
        return status[state]
Пример #8
0
 def check_base(self):
     os.chdir(self.sage_root)
     try:
         do_or_die("git checkout patchbot/base")
     except Exception:
         do_or_die("git checkout -b patchbot/base")
     do_or_die("git fetch %s +%s:patchbot/base_upstream" % (self.config['base_repo'], self.config['base_branch']))
     only_in_base = int(subprocess.check_output(["git", "rev-list", "--count", "patchbot/base_upstream..patchbot/base"]))
     only_in_upstream = int(subprocess.check_output(["git", "rev-list", "--count", "patchbot/base..patchbot/base_upstream"]))
     if (only_in_base > 0
         or only_in_upstream > self.config['max_behind_commits']
         or (only_in_upstream > 0 and time.time() - self.last_pull < self.config['max_behind_days'] * 60 * 60 * 24)):
         do_or_die("git checkout patchbot/base_upstream")
         do_or_die("git branch -f patchbot/base patchbot/base_upstream")
         do_or_die("git checkout patchbot/base")
         self.last_pull = time.time()
         self.behind_base = {}
         return False
     return True
Пример #9
0
def pull_from_trac(sage_root, ticket, branch=None, force=None, interactive=None, inplace=None, use_ccache=False):
    # There are four branches at play here:
    # patchbot/base -- the latest release that all tickets are merged into for testing
    # patchbot/base_upstream -- temporary staging area for patchbot/base
    # patchbot/ticket_upstream -- pristine clone of the ticket on trac
    # patchbot/ticket_merged -- merge of patchbot/ticket_upstream into patchbot/base
    ticket_id = ticket
    info = scrape(ticket_id)
    os.chdir(sage_root)
    do_or_die("git checkout patchbot/base")
    if ticket_id == 0:
        do_or_die("git branch -f patchbot/ticket_upstream patchbot/base")
        do_or_die("git branch -f patchbot/ticket_merged patchbot/base")
        return
    branch = info['git_branch']
    repo = info['git_repo']
    do_or_die("git fetch %s +%s:patchbot/ticket_upstream" % (repo, branch))
    do_or_die("git rev-list --left-right --count patchbot/base..patchbot/ticket_upstream")
    do_or_die("git branch -f patchbot/ticket_merged patchbot/base")
    do_or_die("git checkout patchbot/ticket_merged")
    try:
        do_or_die("git merge -X patience patchbot/ticket_upstream")
    except Exception:
        do_or_die("git merge --abort")
        raise
    if not inplace_safe():
        tmp_dir = tempfile.mkdtemp("-sage-git-temp-%s" % ticket_id)
        do_or_die("git clone . '%s'" % tmp_dir)
        os.chdir(tmp_dir)
        os.symlink(os.path.join(sage_root, "upstream"), "upstream")
        os.environ['SAGE_ROOT'] = tmp_dir
        do_or_die("git branch -f patchbot/base remotes/origin/patchbot/base")
        do_or_die("git branch -f patchbot/ticket_upstream remotes/origin/patchbot/ticket_upstream")
        if use_ccache:
            do_or_die("./sage -i ccache")
Пример #10
0
    def test_a_ticket(self, ticket=None):
    
        self.reload_config()

        if ticket is None:
            ticket = self.get_ticket()
        else:
            ticket = None, scrape(int(ticket))
        if not ticket:
            print "No more tickets."
            time.sleep(conf['idle'])
            return

        rating, ticket = ticket
        print "\n" * 2
        print "=" * 30, ticket['id'], "=" * 30
        print ticket['title']
        print "score", rating
        print "\n" * 2
        log_dir = self.sage_root + "/logs"
        if not os.path.exists(log_dir):
            os.mkdir(log_dir)
        log = '%s/%s-log.txt' % (log_dir, ticket['id'])
        if not self.plugin_only:
            self.report_ticket(ticket, status='Pending', log=None)
        plugins_results = []
        try:
            with Tee(log, time=True, timeout=self.config['timeout']):
                t = Timer()
                start_time = time.time()

                if ticket['spkgs']:
                    state = 'spkg'
                    print "\n".join(ticket['spkgs'])
                    print
                    for spkg in ticket['spkgs']:
                        print
                        print '+' * 10, spkg, '+' * 10
                        print
                        try:
                            self.check_spkg(spkg)
                        except Exception:
                            traceback.print_exc()
                        t.finish(spkg)

                else:
                    state = 'started'
                    os.environ['MAKE'] = "make -j%s" % self.config['parallelism']
                    os.environ['SAGE_ROOT'] = self.sage_root
                # TODO: Ensure that sage-main is pristine.
                    pull_from_trac(self.sage_root, ticket['id'], force=True)
                    t.finish("Apply")
                    state = 'applied'
                
                    do_or_die('$SAGE_ROOT/sage -b %s' % ticket['id'])
                    t.finish("Build")
                    state = 'built'
                
                    working_dir = "%s/devel/sage-%s" % (self.sage_root, ticket['id'])
                # Only the ones on this ticket.
                    patches = os.popen2('hg --cwd %s qapplied' % working_dir)[1].read().strip().split('\n')[-len(ticket['patches']):]
                    kwds = {
                        "original_dir": "%s/devel/sage-0" % self.sage_root,
                        "patched_dir": working_dir,
                        "patches": ["%s/devel/sage-%s/.hg/patches/%s" % (self.sage_root, ticket['id'], p) for p in patches if p],
                        "sage_binary": os.path.join(self.sage_root, 'sage')
                        }
                
                    for name, plugin in self.config['plugins']:
                        try:
                            if ticket['id'] != 0 and os.path.exists(os.path.join(log_dir, '0', name)):
                                baseline = pickle.load(open(os.path.join(log_dir, '0', name)))
                            else:
                                baseline = None
                            print plugin_boundary(name)
                            res = plugin(ticket, baseline=baseline, **kwds)
                            passed = True
                        except Exception:
                            traceback.print_exc()
                            passed = False
                            res = None
                        finally:
                            if isinstance(res, PluginResult):
                                if res.baseline is not None:
                                    plugin_dir = os.path.join(log_dir, str(ticket['id']))
                                    if not os.path.exists(plugin_dir):
                                        os.mkdir(plugin_dir)
                                    pickle.dump(res.baseline, open(os.path.join(plugin_dir, name), 'w'))
                                    passed = res.status == PluginResult.Passed
                                    print name, res.status
                                    plugins_results.append((name, passed, res.data))
                                else:
                                    plugins_results.append((name, passed, None))
                            t.finish(name)
                            print plugin_boundary(name, end=True)
                    plugins_passed = all(passed for (name, passed, data) in plugins_results)
                
                    if self.plugin_only:
                        state = 'plugins' if plugins_passed else 'plugins_failed'
                    else: 
                        if self.dry_run:
                            test_dirs = ["$SAGE_ROOT/devel/sage-%s/sage/misc/a*.py" % (ticket['id'])]
                        else: 
                            test_dirs = ["--sagenb"] + ["$SAGE_ROOT/devel/sage-%s/%s" % (ticket['id'], dir) for dir in all_test_dirs]
                        if conf['parallelism'] > 1:
                            test_cmd = "-tp %s" % conf['parallelism']
                        else: 
                            test_cmd = "-t"
                        do_or_die("$SAGE_ROOT/sage %s %s" % (test_cmd, ' '.join(test_dirs)))
                        t.finish("Tests")
                        state = 'tested'
                    
                        if not plugins_passed:
                            state = 'tests_passed_plugins_failed'

                print
                t.print_all()
        except urllib2.HTTPError:
            # Don't report failure because the network/trac died...
            traceback.print_exc()
            return 'Pending'
        except Exception:
            traceback.print_exc()
        
        for _ in range(5):
            try:
                print "Reporting", ticket['id'], status[state]
                if not self.dry_run:
                    self.report_ticket(ticket, status=status[state], log=log, plugins=plugins_results)
                print "Done reporting", ticket['id']
                break
            except urllib2.HTTPError:
                traceback.print_exc()
                time.sleep(conf['idle'])
        else:
            print "Error reporting", ticket['id']
        if not conf['keep_open_branches'] and str(ticket['id']) != '0' and not ticket['spkgs']:
            shutil.rmtree(os.path.join(self.sage_root, "devel", "sage-%s" %ticket['id']))
        return status[state]
Пример #11
0
def main(args):
    """
    Most configuration is done in the config file, which is reread between
    each ticket for live configuration of the patchbot.
    """
    global conf
    parser = OptionParser()
    parser.add_option("--config", dest="config")
    parser.add_option("--sage-root", dest="sage_root", default=os.environ.get('SAGE_ROOT'))
    parser.add_option("--server", dest="server", default="http://patchbot.sagemath.org/")
    parser.add_option("--count", dest="count", default=1000000)
    parser.add_option("--ticket", dest="ticket", default=None)
    parser.add_option("--list", dest="list", default=False)
    parser.add_option("--full", action="store_true", dest="full", default=False)
    parser.add_option("--skip-base", action="store_true", dest="skip_base", default=False)
    parser.add_option("--dry-run", action="store_true", dest="dry_run", default=False)
    parser.add_option("--plugin-only", action="store_true", dest="plugin_only", default=False)
    parser.add_option("--cleanup", action="store_true", dest="cleanup", default=False)
    parser.add_option("--safe-only", action="store_true", dest="safe_only", default=False)
    parser.add_option("--interactive", action="store_true", dest="interactive", default=False)
    (options, args) = parser.parse_args(args)

    conf_path = options.config and os.path.abspath(options.config)
    if options.ticket:
        tickets = [int(t) for t in options.ticket.split(',')]
        count = len(tickets)
    else:
        tickets = None
        count = int(options.count)

    patchbot = Patchbot(os.path.abspath(options.sage_root), options.server, conf_path, dry_run=options.dry_run, plugin_only=options.plugin_only, options=options)

    conf = patchbot.get_config()
    if options.list:
        count = sys.maxint if options.list is "True" else int(options.list)
        print "Getting ticket list..."
        for ix, (score, ticket) in enumerate(patchbot.get_ticket_list()):
            if ix >= count:
                break
            print score, '\t', ticket['id'], '\t', ticket['title']
            if options.full:
                print ticket
                print
        sys.exit(0)

    if options.sage_root == os.environ.get('SAGE_ROOT'):
        print "WARNING: Do not use this copy of sage while the patchbot is running."
    ensure_free_space(options.sage_root)

    log_dir = options.sage_root + "/logs"
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)
    # Make sure this file is writable.
    handle = open(os.path.join(log_dir, 'install.log'), 'a')
    handle.close()

    if conf['use_ccache']:
        do_or_die("'%s'/sage -i ccache" % options.sage_root, exn_class=ConfigException)
        # If we rebuild the (same) compiler we still want to share the cache.
        os.environ['CCACHE_COMPILERCHECK'] = '%compiler% --version'

    if not options.skip_base:
        patchbot.check_base()
        def good(report):
            return report['machine'] == conf['machine'] and report['status'] == 'TestsPassed'
        if options.plugin_only or not any(good(report) for report in patchbot.current_reports(0)):
            res = patchbot.test_a_ticket(0)
            if res not in  ('TestsPassed', 'PluginOnly'):
                print "\n\n"
                print "Current base:", conf['base_repo'], conf['base_branch']
                if not options.interactive:
                    print "Failing tests in your base install: exiting."
                    sys.exit(1)
                while True:
                    print "Failing tests in your base install: %s. Continue anyways? [y/N] " % res
                    try:
                        ans = sys.stdin.readline().lower().strip()
                    except IOError:
                        # Might not be interactive.
                        print "Non interactive, not continuing."
                        ans = 'n'
                    if ans == '' or ans[0] == 'n':
                        sys.exit(1)
                    elif ans[0] == 'y':
                        break

    for k in range(count):
        if options.cleanup:
            for path in glob.glob(os.path.join(tempfile.gettempdir(), "*%s*" % temp_build_suffix)):
                print "Cleaning up ", path
                shutil.rmtree(path)
        try:
            if tickets:
                ticket = tickets.pop(0)
            else:
                ticket = None
            conf = patchbot.reload_config()
            if check_time_of_day(conf['time_of_day']):
                if not patchbot.check_base():
                    patchbot.test_a_ticket(0)
                patchbot.test_a_ticket(ticket)
            else:
                print "Idle."
                time.sleep(conf['idle'])
        except Exception:
            traceback.print_exc()
            time.sleep(conf['idle'])