Commit e7eac728 authored by Al Muthanna Athamina's avatar Al Muthanna Athamina Committed by V8 LUCI CQ

Remove deprecated scripts in V8

Bug: chromium:1306416
Change-Id: I103602ed1bea71d79a17a9a37c7eaf198575d371
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3521944Reviewed-by: 's avatarMichael Achenbach <machenbach@chromium.org>
Reviewed-by: 's avatarClemens Backes <clemensb@chromium.org>
Commit-Queue: Almothana Athamneh <almuthanna@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79483}
parent 4e983705
#!/usr/bin/env python3
# Copyright 2014 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import sys
from common_includes import *
class Preparation(Step):
MESSAGE = "Preparation."
def RunStep(self):
# TODO(machenbach): Remove after the git switch.
if self.Config("PERSISTFILE_BASENAME") == "/tmp/v8-auto-tag-tempfile":
print("This script is disabled until after the v8 git migration.")
return True
self.CommonPrepare()
self.PrepareBranch()
self.GitCheckout("main")
self.vc.Pull()
class GetTags(Step):
MESSAGE = "Get all V8 tags."
def RunStep(self):
self.GitCreateBranch(self._config["BRANCHNAME"])
self["tags"] = self.vc.GetTags()
class GetOldestUntaggedVersion(Step):
MESSAGE = "Check if there's a version on bleeding edge without a tag."
def RunStep(self):
tags = set(self["tags"])
self["candidate"] = None
self["candidate_version"] = None
self["next"] = None
self["next_version"] = None
# Iterate backwards through all automatic version updates.
for git_hash in self.GitLog(
format="%H", grep="\\[Auto\\-roll\\] Bump up version to").splitlines():
# Get the version.
if not self.GitCheckoutFileSafe(VERSION_FILE, git_hash):
continue
self.ReadAndPersistVersion()
version = self.ArrayToVersion("")
# Strip off trailing patch level (tags don't include tag level 0).
if version.endswith(".0"):
version = version[:-2]
# Clean up checked-out version file.
self.GitCheckoutFileSafe(VERSION_FILE, "HEAD")
if version in tags:
if self["candidate"]:
# Revision "git_hash" is tagged already and "candidate" was the next
# newer revision without a tag.
break
else:
print("Stop as %s is the latest version and it has been tagged." %
version)
self.CommonCleanup()
return True
else:
# This is the second oldest version without a tag.
self["next"] = self["candidate"]
self["next_version"] = self["candidate_version"]
# This is the oldest version without a tag.
self["candidate"] = git_hash
self["candidate_version"] = version
if not self["candidate"] or not self["candidate_version"]:
print("Nothing found to tag.")
self.CommonCleanup()
return True
print("Candidate for tagging is %s with version %s" %
(self["candidate"], self["candidate_version"]))
class GetLKGRs(Step):
MESSAGE = "Get the last lkgrs."
def RunStep(self):
revision_url = "https://v8-status.appspot.com/revisions?format=json"
status_json = self.ReadURL(revision_url, wait_plan=[5, 20])
self["lkgrs"] = [entry["revision"]
for entry in json.loads(status_json) if entry["status"]]
class CalculateTagRevision(Step):
MESSAGE = "Calculate the revision to tag."
def LastLKGR(self, min_rev, max_rev):
"""Finds the newest lkgr between min_rev (inclusive) and max_rev
(exclusive).
"""
for lkgr in self["lkgrs"]:
# LKGRs are reverse sorted.
if int(min_rev) <= int(lkgr) and int(lkgr) < int(max_rev):
return lkgr
return None
def RunStep(self):
# Get the lkgr after the tag candidate and before the next tag candidate.
candidate_svn = self.vc.GitSvn(self["candidate"])
if self["next"]:
next_svn = self.vc.GitSvn(self["next"])
else:
# Don't include the version change commit itself if there is no upper
# limit yet.
candidate_svn = str(int(candidate_svn) + 1)
next_svn = sys.maxsize
lkgr_svn = self.LastLKGR(candidate_svn, next_svn)
if not lkgr_svn:
print("There is no lkgr since the candidate version yet.")
self.CommonCleanup()
return True
# Let's check if the lkgr is at least three hours old.
self["lkgr"] = self.vc.SvnGit(lkgr_svn)
if not self["lkgr"]:
print("Couldn't find git hash for lkgr %s" % lkgr_svn)
self.CommonCleanup()
return True
lkgr_utc_time = int(self.GitLog(n=1, format="%at", git_hash=self["lkgr"]))
current_utc_time = self._side_effect_handler.GetUTCStamp()
if current_utc_time < lkgr_utc_time + 10800:
print("Candidate lkgr %s is too recent for tagging." % lkgr_svn)
self.CommonCleanup()
return True
print("Tagging revision %s with %s" % (lkgr_svn, self["candidate_version"]))
class MakeTag(Step):
MESSAGE = "Tag the version."
def RunStep(self):
if not self._options.dry_run:
self.GitReset(self["lkgr"])
# FIXME(machenbach): Make this work with the git repo.
self.vc.Tag(self["candidate_version"],
"svn/bleeding_edge",
"This won't work!")
class CleanUp(Step):
MESSAGE = "Clean up."
def RunStep(self):
self.CommonCleanup()
class AutoTag(ScriptsBase):
def _PrepareOptions(self, parser):
parser.add_argument("--dry_run", help="Don't tag the new version.",
default=False, action="store_true")
def _ProcessOptions(self, options): # pragma: no cover
if not options.dry_run and not options.author:
print("Specify your chromium.org email with -a")
return False
options.wait_for_lgtm = False
options.force_readline_defaults = True
options.force_upload = True
return True
def _Config(self):
return {
"BRANCHNAME": "auto-tag-v8",
"PERSISTFILE_BASENAME": "/tmp/v8-auto-tag-tempfile",
}
def _Steps(self):
return [
Preparation,
GetTags,
GetOldestUntaggedVersion,
GetLKGRs,
CalculateTagRevision,
MakeTag,
CleanUp,
]
if __name__ == "__main__": # pragma: no cover
sys.exit(AutoTag().Run())
#!/usr/bin/env python
# Copyright 2014 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Script to check for new clusterfuzz issues since the last rolled v8 revision.
Returns a json list with test case IDs if any.
Security considerations: The security key and request data must never be
written to public logs. Public automated callers of this script should
suppress stdout and stderr and only process contents of the results_file.
"""
# for py2/py3 compatibility
from __future__ import print_function
import argparse
import httplib
import json
import os
import re
import sys
import urllib
import urllib2
# Constants to git repos.
BASE_URL = "https://chromium.googlesource.com"
DEPS_LOG = BASE_URL + "/chromium/src/+log/main/DEPS?format=JSON"
# Constants for retrieving v8 rolls.
CRREV = "https://cr-rev.appspot.com/_ah/api/crrev/v1/commit/%s"
V8_COMMIT_RE = re.compile(
r"^Update V8 to version \d+\.\d+\.\d+ \(based on ([a-fA-F0-9]+)\)\..*")
# Constants for the clusterfuzz backend.
HOSTNAME = "backend-dot-cluster-fuzz.appspot.com"
# Crash patterns.
V8_INTERNAL_RE = re.compile(r"^v8::internal.*")
ANY_RE = re.compile(r".*")
# List of all api requests.
BUG_SPECS = [
{
"args": {
"job_type": "linux_asan_chrome_v8",
"reproducible": "True",
"open": "True",
"bug_information": "",
},
"crash_state": V8_INTERNAL_RE,
},
{
"args": {
"job_type": "linux_asan_d8",
"reproducible": "True",
"open": "True",
"bug_information": "",
},
"crash_state": ANY_RE,
},
{
"args": {
"job_type": "linux_asan_d8_dbg",
"reproducible": "True",
"open": "True",
"bug_information": "",
},
"crash_state": ANY_RE,
},
{
"args": {
"job_type": "linux_asan_d8_ignition_dbg",
"reproducible": "True",
"open": "True",
"bug_information": "",
},
"crash_state": ANY_RE,
},
{
"args": {
"job_type": "linux_asan_d8_v8_arm_dbg",
"reproducible": "True",
"open": "True",
"bug_information": "",
},
"crash_state": ANY_RE,
},
{
"args": {
"job_type": "linux_asan_d8_ignition_v8_arm_dbg",
"reproducible": "True",
"open": "True",
"bug_information": "",
},
"crash_state": ANY_RE,
},
{
"args": {
"job_type": "linux_asan_d8_v8_arm64_dbg",
"reproducible": "True",
"open": "True",
"bug_information": "",
},
"crash_state": ANY_RE,
},
{
"args": {
"job_type": "linux_asan_d8_v8_mipsel_dbg",
"reproducible": "True",
"open": "True",
"bug_information": "",
},
"crash_state": ANY_RE,
},
]
def GetRequest(url):
url_fh = urllib2.urlopen(url, None, 60)
try:
return url_fh.read()
finally:
url_fh.close()
def GetLatestV8InChromium():
"""Returns the commit position number of the latest v8 roll in chromium."""
# Check currently rolled v8 revision.
result = GetRequest(DEPS_LOG)
if not result:
return None
# Strip security header and load json.
commits = json.loads(result[5:])
git_revision = None
for commit in commits["log"]:
# Get latest commit that matches the v8 roll pattern. Ignore cherry-picks.
match = re.match(V8_COMMIT_RE, commit["message"])
if match:
git_revision = match.group(1)
break
else:
return None
# Get commit position number for v8 revision.
result = GetRequest(CRREV % git_revision)
if not result:
return None
commit = json.loads(result)
assert commit["repo"] == "v8/v8"
return commit["number"]
def APIRequest(key, **params):
"""Send a request to the clusterfuzz api.
Returns a json dict of the response.
"""
params["api_key"] = key
params = urllib.urlencode(params)
headers = {"Content-type": "application/x-www-form-urlencoded"}
try:
conn = httplib.HTTPSConnection(HOSTNAME)
conn.request("POST", "/_api/", params, headers)
response = conn.getresponse()
# Never leak "data" into public logs.
data = response.read()
except:
raise Exception("ERROR: Connection problem.")
try:
return json.loads(data)
except:
raise Exception("ERROR: Could not read response. Is your key valid?")
return None
def Main():
parser = argparse.ArgumentParser()
parser.add_argument("-k", "--key-file", required=True,
help="A file with the clusterfuzz api key.")
parser.add_argument("-r", "--results-file",
help="A file to write the results to.")
options = parser.parse_args()
# Get api key. The key's content must never be logged.
assert options.key_file
with open(options.key_file) as f:
key = f.read().strip()
assert key
revision_number = GetLatestV8InChromium()
results = []
for spec in BUG_SPECS:
args = dict(spec["args"])
# Use incremented revision as we're interested in all revision greater than
# what's currently rolled into chromium.
if revision_number:
args["revision_greater_or_equal"] = str(int(revision_number) + 1)
# Never print issue details in public logs.
issues = APIRequest(key, **args)
assert issues is not None
for issue in issues:
if (re.match(spec["crash_state"], issue["crash_state"]) and
not issue.get('has_bug_flag')):
results.append(issue["id"])
if options.results_file:
with open(options.results_file, "w") as f:
f.write(json.dumps(results))
else:
print(results)
if __name__ == "__main__":
sys.exit(Main())
#!/usr/bin/env python
# Copyright 2015 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# for py2/py3 compatibility
from __future__ import print_function
import argparse
import operator
import os
import re
from sets import Set
from subprocess import Popen, PIPE
import sys
def search_all_related_commits(
git_working_dir, start_hash, until, separator, verbose=False):
all_commits_raw = _find_commits_inbetween(
start_hash, until, git_working_dir, verbose)
if verbose:
print("All commits between <of> and <until>: " + all_commits_raw)
# Adding start hash too
all_commits = [start_hash]
all_commits.extend(all_commits_raw.splitlines())
all_related_commits = {}
already_treated_commits = Set([])
for commit in all_commits:
if commit in already_treated_commits:
continue
related_commits = _search_related_commits(
git_working_dir, commit, until, separator, verbose)
if len(related_commits) > 0:
all_related_commits[commit] = related_commits
already_treated_commits.update(related_commits)
already_treated_commits.update(commit)
return all_related_commits
def _search_related_commits(
git_working_dir, start_hash, until, separator, verbose=False):
if separator:
commits_between = _find_commits_inbetween(
start_hash, separator, git_working_dir, verbose)
if commits_between == "":
return []
# Extract commit position
original_message = git_execute(
git_working_dir,
["show", "-s", "--format=%B", start_hash],
verbose)
title = original_message.splitlines()[0]
matches = re.search("(\{#)([0-9]*)(\})", original_message)
if not matches:
return []
commit_position = matches.group(2)
if verbose:
print("1.) Commit position to look for: " + commit_position)
search_range = start_hash + ".." + until
def git_args(grep_pattern):
return [
"log",
"--reverse",
"--grep=" + grep_pattern,
"--format=%H",
search_range,
]
found_by_hash = git_execute(
git_working_dir, git_args(start_hash), verbose).strip()
if verbose:
print("2.) Found by hash: " + found_by_hash)
found_by_commit_pos = git_execute(
git_working_dir, git_args(commit_position), verbose).strip()
if verbose:
print("3.) Found by commit position: " + found_by_commit_pos)
# Replace brackets or else they are wrongly interpreted by --grep
title = title.replace("[", "\\[")
title = title.replace("]", "\\]")
found_by_title = git_execute(
git_working_dir, git_args(title), verbose).strip()
if verbose:
print("4.) Found by title: " + found_by_title)
hits = (
_convert_to_array(found_by_hash) +
_convert_to_array(found_by_commit_pos) +
_convert_to_array(found_by_title))
hits = _remove_duplicates(hits)
if separator:
for current_hit in hits:
commits_between = _find_commits_inbetween(
separator, current_hit, git_working_dir, verbose)
if commits_between != "":
return hits
return []
return hits
def _find_commits_inbetween(start_hash, end_hash, git_working_dir, verbose):
commits_between = git_execute(
git_working_dir,
["rev-list", "--reverse", start_hash + ".." + end_hash],
verbose)
return commits_between.strip()
def _convert_to_array(string_of_hashes):
return string_of_hashes.splitlines()
def _remove_duplicates(array):
no_duplicates = []
for current in array:
if not current in no_duplicates:
no_duplicates.append(current)
return no_duplicates
def git_execute(working_dir, args, verbose=False):
command = ["git", "-C", working_dir] + args
if verbose:
print("Git working dir: " + working_dir)
print("Executing git command:" + str(command))
p = Popen(args=command, stdin=PIPE,
stdout=PIPE, stderr=PIPE)
output, err = p.communicate()
rc = p.returncode
if rc != 0:
raise Exception(err)
if verbose:
print("Git return value: " + output)
return output
def _pretty_print_entry(hash, git_dir, pre_text, verbose):
text_to_print = git_execute(
git_dir,
["show",
"--quiet",
"--date=iso",
hash,
"--format=%ad # %H # %s"],
verbose)
return pre_text + text_to_print.strip()
def main(options):
all_related_commits = search_all_related_commits(
options.git_dir,
options.of[0],
options.until[0],
options.separator,
options.verbose)
sort_key = lambda x: (
git_execute(
options.git_dir,
["show", "--quiet", "--date=iso", x, "--format=%ad"],
options.verbose)).strip()
high_level_commits = sorted(all_related_commits.keys(), key=sort_key)
for current_key in high_level_commits:
if options.prettyprint:
yield _pretty_print_entry(
current_key,
options.git_dir,
"+",
options.verbose)
else:
yield "+" + current_key
found_commits = all_related_commits[current_key]
for current_commit in found_commits:
if options.prettyprint:
yield _pretty_print_entry(
current_commit,
options.git_dir,
"| ",
options.verbose)
else:
yield "| " + current_commit
if __name__ == "__main__": # pragma: no cover
parser = argparse.ArgumentParser(
"This tool analyzes the commit range between <of> and <until>. "
"It finds commits which belong together e.g. Implement/Revert pairs and "
"Implement/Port/Revert triples. All supplied hashes need to be "
"from the same branch e.g. main.")
parser.add_argument("-g", "--git-dir", required=False, default=".",
help="The path to your git working directory.")
parser.add_argument("--verbose", action="store_true",
help="Enables a very verbose output")
parser.add_argument("of", nargs=1,
help="Hash of the commit to be searched.")
parser.add_argument("until", nargs=1,
help="Commit when searching should stop")
parser.add_argument("--separator", required=False,
help="The script will only list related commits "
"which are separated by hash <--separator>.")
parser.add_argument("--prettyprint", action="store_true",
help="Pretty prints the output")
args = sys.argv[1:]
options = parser.parse_args(args)
for current_line in main(options):
print(current_line)
......@@ -42,7 +42,6 @@ import create_release
from create_release import *
import merge_to_branch
from merge_to_branch import MergeToBranch
from auto_tag import AutoTag
import roll_merge
from roll_merge import RollMerge
......
#!/usr/bin/env python
# Copyright 2015 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from collections import namedtuple
from os import path
import search_related_commits
import shutil
from subprocess import Popen, PIPE, check_call
import unittest
TEST_CONFIG = {
"GIT_REPO": "/tmp/test-v8-search-related-commits",
}
class TestSearchRelatedCommits(unittest.TestCase):
base_dir = TEST_CONFIG["GIT_REPO"]
def _execute_git(self, git_args):
fullCommand = ["git", "-C", self.base_dir] + git_args
p = Popen(args=fullCommand, stdin=PIPE,
stdout=PIPE, stderr=PIPE)
output, err = p.communicate()
rc = p.returncode
if rc != 0:
raise Exception(err)
return output
def setUp(self):
if path.exists(self.base_dir):
shutil.rmtree(self.base_dir)
check_call(["git", "init", self.base_dir])
# Initial commit
message = """[turbofan] Sanitize language mode for javascript operators.
R=mstarzinger@chromium.org
Review URL: https://codereview.chromium.org/1084243005
Cr-Commit-Position: refs/heads/main@{#28059}"""
self._make_empty_commit(message)
message = """[crankshaft] Do some stuff
R=hablich@chromium.org
Review URL: https://codereview.chromium.org/1084243007
Cr-Commit-Position: refs/heads/main@{#28030}"""
self._make_empty_commit(message)
def tearDown(self):
if path.exists(self.base_dir):
shutil.rmtree(self.base_dir)
def _assert_correct_standard_result(
self, result, all_commits, hash_of_first_commit):
self.assertEqual(len(result), 1, "Main commit not found")
self.assertTrue(
result.get(hash_of_first_commit),
"Main commit is wrong")
self.assertEqual(
len(result[hash_of_first_commit]),
1,
"Child commit not found")
self.assertEqual(
all_commits[2],
result[hash_of_first_commit][0],
"Child commit wrong")
def _get_commits(self):
commits = self._execute_git(
["log", "--format=%H", "--reverse"]).splitlines()
return commits
def _make_empty_commit(self, message):
self._execute_git(["commit", "--allow-empty", "-m", message])
def testSearchByCommitPosition(self):
message = """Revert of some stuff.
> Cr-Commit-Position: refs/heads/main@{#28059}
R=mstarzinger@chromium.org
Review URL: https://codereview.chromium.org/1084243005
Cr-Commit-Position: refs/heads/main@{#28088}"""
self._make_empty_commit(message)
commits = self._get_commits()
hash_of_first_commit = commits[0]
result = search_related_commits.search_all_related_commits(
self.base_dir, hash_of_first_commit, "HEAD", None)
self._assert_correct_standard_result(result, commits, hash_of_first_commit)
def testSearchByTitle(self):
message = """Revert of some stuff.
> [turbofan] Sanitize language mode for javascript operators.
> Cr-Commit-Position: refs/heads/main@{#289}
R=mstarzinger@chromium.org
Review URL: https://codereview.chromium.org/1084243005
Cr-Commit-Position: refs/heads/main@{#28088}"""
self._make_empty_commit(message)
commits = self._get_commits()
hash_of_first_commit = commits[0]
result = search_related_commits.search_all_related_commits(
self.base_dir, hash_of_first_commit, "HEAD", None)
self._assert_correct_standard_result(result, commits, hash_of_first_commit)
def testSearchByHash(self):
commits = self._get_commits()
hash_of_first_commit = commits[0]
message = """Revert of some stuff.
> [turbofan] Sanitize language mode for javascript operators.
> Reverting """ + hash_of_first_commit + """
> R=mstarzinger@chromium.org
Review URL: https://codereview.chromium.org/1084243005
Cr-Commit-Position: refs/heads/main@{#28088}"""
self._make_empty_commit(message)
#Fetch again for an update
commits = self._get_commits()
hash_of_first_commit = commits[0]
result = search_related_commits.search_all_related_commits(
self.base_dir,
hash_of_first_commit,
"HEAD",
None)
self._assert_correct_standard_result(result, commits, hash_of_first_commit)
def testConsiderSeparator(self):
commits = self._get_commits()
hash_of_first_commit = commits[0]
# Related commits happen before separator so it is not a hit
message = """Revert of some stuff: Not a hit
> [turbofan] Sanitize language mode for javascript operators.
> Reverting """ + hash_of_first_commit + """
> R=mstarzinger@chromium.org
Review URL: https://codereview.chromium.org/1084243005
Cr-Commit-Position: refs/heads/main@{#28088}"""
self._make_empty_commit(message)
# Related commits happen before and after separator so it is a hit
commit_pos_of_main = "27088"
message = """Implement awesome feature: Main commit
Review URL: https://codereview.chromium.org/1084243235
Cr-Commit-Position: refs/heads/main@{#""" + commit_pos_of_main + "}"
self._make_empty_commit(message)
# Separator commit
message = """Commit which is the origin of the branch
Review URL: https://codereview.chromium.org/1084243456
Cr-Commit-Position: refs/heads/main@{#28173}"""
self._make_empty_commit(message)
# Filler commit
message = "Some unrelated commit: Not a hit"
self._make_empty_commit(message)
# Related commit after separator: a hit
message = "Patch r" + commit_pos_of_main +""" done
Review URL: https://codereview.chromium.org/1084243235
Cr-Commit-Position: refs/heads/main@{#29567}"""
self._make_empty_commit(message)
#Fetch again for an update
commits = self._get_commits()
hash_of_first_commit = commits[0]
hash_of_hit = commits[3]
hash_of_separator = commits[4]
hash_of_child_hit = commits[6]
result = search_related_commits.search_all_related_commits(
self.base_dir,
hash_of_first_commit,
"HEAD",
hash_of_separator)
self.assertTrue(result.get(hash_of_hit), "Hit not found")
self.assertEqual(len(result), 1, "More than one hit found")
self.assertEqual(
len(result.get(hash_of_hit)),
1,
"More than one child hit found")
self.assertEqual(
result.get(hash_of_hit)[0],
hash_of_child_hit,
"Wrong commit found")
def testPrettyPrint(self):
message = """Revert of some stuff.
> [turbofan] Sanitize language mode for javascript operators.
> Cr-Commit-Position: refs/heads/main@{#289}
R=mstarzinger@chromium.org
Review URL: https://codereview.chromium.org/1084243005
Cr-Commit-Position: refs/heads/main@{#28088}"""
self._make_empty_commit(message)
commits = self._get_commits()
hash_of_first_commit = commits[0]
OptionsStruct = namedtuple(
"OptionsStruct",
"git_dir of until all prettyprint separator verbose")
options = OptionsStruct(
git_dir= self.base_dir,
of= [hash_of_first_commit],
until= [commits[2]],
all= True,
prettyprint= True,
separator = None,
verbose=False)
output = []
for current_line in search_related_commits.main(options):
output.append(current_line)
self.assertIs(len(output), 2, "Not exactly two entries written")
self.assertTrue(output[0].startswith("+"), "Main entry not marked with +")
self.assertTrue(output[1].startswith("| "), "Child entry not marked with |")
def testNothingFound(self):
commits = self._get_commits()
self._execute_git(["commit", "--allow-empty", "-m", "A"])
self._execute_git(["commit", "--allow-empty", "-m", "B"])
self._execute_git(["commit", "--allow-empty", "-m", "C"])
self._execute_git(["commit", "--allow-empty", "-m", "D"])
hash_of_first_commit = commits[0]
result = search_related_commits.search_all_related_commits(
self.base_dir,
hash_of_first_commit,
"HEAD",
None)
self.assertEqual(len(result), 0, "Results found where none should be.")
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment