Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Unified Diff: components/test/data/password_manager/automated_tests/run_tests.py

Issue 903763003: Refactoring and paralelizing Python password manager tests. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/test/data/password_manager/automated_tests/run_tests.py
diff --git a/components/test/data/password_manager/automated_tests/run_tests.py b/components/test/data/password_manager/automated_tests/run_tests.py
index 4e05853faeb56b3844997796417c09724166b747..48c549a9c80b3e835cf3cea5acac26bf1efacfe0 100644
--- a/components/test/data/password_manager/automated_tests/run_tests.py
+++ b/components/test/data/password_manager/automated_tests/run_tests.py
@@ -7,190 +7,213 @@
Running this script requires passing --config-path with a path to a config file
of the following structure:
- [credentials]
+ [data_files]
+ passwords_path=<path to a file with passwords>
+ [binaries]
+ chrome-path=<chrome binary path>
melandory 2015/02/06 08:37:47 Along with changes in run_tests you/I/someone shou
dvadym 2015/02/06 11:02:26 Thanks, I'll do.
+ chromedriver-path=<chrome driver path>
+ [run_options]
+ write_to_sheet=[false|true]
+ tests_in_parrallel=<number of parrallel tests>
vabr (Chromium) 2015/02/05 17:49:58 parrallel -> parallel
dvadym 2015/02/06 11:02:27 Done.
+ # |tests_to_runs| field is optional, if it is absent all tests will be run.
+ tests_to_run=<test names to run, comma delimited>
+ [output]
+ save-path=<file where to save result>
+ [sheet_info]
+ # This section is required only when write_to_sheet=true
pkey=full_path
client_email=email_assigned_by_google_dev_console
- [drive]
- key=sheet_key_from_sheet_url
- [data_files]
- passwords_path=full_path_to_the_file_with_passwords
+ sheet_key=sheet_key_from_sheet_url
"""
-from Sheet import Sheet
-from apiclient.discovery import build
from datetime import datetime
-from gdata.gauth import OAuth2TokenFromCredentials
-from gdata.spreadsheet.service import SpreadsheetsService
-from oauth2client.client import SignedJwtAssertionCredentials
import ConfigParser
-import argparse
+import sys
import httplib2
-import oauth2client.tools
import os
import subprocess
import tempfile
+import time
+sheet_libraries_import_error = None
+try:
+ from Sheet import Sheet
+ from apiclient.discovery import build
+ from gdata.gauth import OAuth2TokenFromCredentials
+ from gdata.spreadsheet.service import SpreadsheetsService
+ from oauth2client.client import SignedJwtAssertionCredentials
+ import oauth2client.tools
+except ImportError as err:
+ sheet_libraries_import_error = err
+
from environment import Environment
import tests
_CREDENTIAL_SCOPES = "https://spreadsheets.google.com/feeds"
-# TODO(melandory): Function _authenticate belongs to separate module.
-def _authenticate(pkey, client_email):
- """ Authenticates user.
-
- Args:
- pkey: Full path to file with private key generated by Google
- Developer Console.
- client_email: Email address corresponding to private key and also
- generated by Google Developer Console.
- """
- http, token = None, None
- with open(pkey) as pkey_file:
- private_key = pkey_file.read()
- credentials = SignedJwtAssertionCredentials(
- client_email, private_key, _CREDENTIAL_SCOPES)
- http = httplib2.Http()
- http = credentials.authorize(http)
- build('drive', 'v2', http=http)
- token = OAuth2TokenFromCredentials(credentials).access_token
- return http, token
-
-# TODO(melandory): Functionality of _spredsheeet_for_logging belongs
-# to websitetests, because this way we do not need to write results of run
-# in separate file and then read it here.
-def _spredsheeet_for_logging(sheet_key, access_token):
- """ Connects to document where result of test run will be logged.
- Args:
- sheet_key: Key of sheet in Trix. Can be found in sheet's URL.
- access_token: Access token of an account which should have edit rights.
- """
- # Connect to trix
- service = SpreadsheetsService(additional_headers={
- "Authorization": "Bearer " + access_token})
- sheet = Sheet(service, sheet_key)
- return sheet
-
-def _try_run_individual_test(test_cmd, results_path):
- """ Runs individual test and logs results to trix.
-
- Args:
- test_cmd: String contains command which runs test.
- results_path: Full path to file where results of test run will be logged.
- """
- failures = []
- # The tests can be flaky. This is why we try to rerun up to 3 times.
- for x in xrange(3):
- # TODO(rchtara): Using "pkill" is just temporary until a better,
- # platform-independent solution is found.
- # Using any kind of kill and process name isn't best solution.
- # Mainly because it kills every running instace of Chrome on the machine,
- # which is unpleasant experience, when you're running tests locally.
- # In my opinion proper solution is to invoke chrome using subproceses and
- # then kill only this particular instance of it.
- os.system("pkill chrome")
+class SheetWriter:
melandory 2015/02/06 08:37:47 Maybe put it to separate file?
dvadym 2015/02/06 11:02:27 It seems that run_tests.py is not so big, having m
vabr (Chromium) 2015/02/06 14:32:52 I don't support the thesis, that multiple files ar
dvadym 2015/02/06 15:00:16 Ok, added Todo
+ def __init__(self, config):
+ self.write_to_sheet = config.getboolean("run_options", "write_to_sheet")
+ if not self.write_to_sheet:
+ return
+ if sheet_libraries_import_error != None:
vabr (Chromium) 2015/02/05 17:49:57 nit: You could drop the != None
dvadym 2015/02/06 11:02:26 Done
+ raise sheet_libraries_import_error
+ self.pkey = config.get("sheet_info", "pkey")
+ self.client_mail=config.get("sheet_info", "client_email")
+ self.sheet_key=config.get("sheet_info", "sheet_key")
+ _, self.access_token = self._authenticate()
+ self.sheet = self._spredsheeet_for_logging()
+
+ # TODO(melandory): Function _authenticate belongs to separate module.
+ def _authenticate(self):
+ http, token = None, None
+ with open(self.pkey) as pkey_file:
+ private_key = pkey_file.read()
+ credentials = SignedJwtAssertionCredentials(
+ self.client_email, private_key, _CREDENTIAL_SCOPES)
+ http = httplib2.Http()
+ http = credentials.authorize(http)
+ build('drive', 'v2', http=http)
+ token = OAuth2TokenFromCredentials(credentials).access_token
+ return http, token
+
+ # TODO(melandory): Functionality of _spredsheeet_for_logging belongs
+ # to websitetests, because this way we do not need to write results of run
+ # in separate file and then read it here.
+ def _spredsheeet_for_logging(self):
+ """ Connects to document where result of test run will be logged. """
+ # Connect to trix
+ service = SpreadsheetsService(additional_headers={
+ "Authorization": "Bearer " + self.access_token})
+ sheet = Sheet(service, self.sheet_key)
+ return sheet
+
+ def write_line_to_sheet(self, data):
+ if not self.write_to_sheet:
+ return
try:
- os.remove(results_path)
+ self.sheet.InsertRow(self.sheet.row_count, data)
except Exception:
- pass
+ pass # TODO(melandory): Sometimes writing to spreadsheet fails. We need
vabr (Chromium) 2015/02/05 17:49:57 grammar: need -> need to
dvadym 2015/02/06 11:02:26 Done.
+ # deal with it better that just ignoring.
vabr (Chromium) 2015/02/05 17:49:57 that -> than ignoring -> by ignoring it
dvadym 2015/02/06 11:02:27 Done.
+
+class TestRunner:
+ def __init__(self, general_test_cmd, test_name):
+ """ Args:
+ general_test_cmd: String contains part of run command common for all tests.
+ test_name: Test name (facebook for example).
+ """
+ print>>sys.stdout, "Test " + test_name + " starting"
vabr (Chromium) 2015/02/05 17:49:57 I'm not sure about the spacing. Did you run gpylin
vabr (Chromium) 2015/02/05 17:49:57 Also, do you need to specify sys.stdout?
dvadym 2015/02/06 11:02:26 Ah, yeah, thanks, at first I used sys.stderr, but
dvadym 2015/02/06 11:02:27 I didn't know about it, I run and fix.
+ self.profile_path = tempfile.mkdtemp()
+ results = tempfile.NamedTemporaryFile(
+ dir=os.path.join(tempfile.gettempdir()), delete=False)
vabr (Chromium) 2015/02/05 17:49:58 What is the effect of calling path.join on a singl
dvadym 2015/02/06 11:02:26 It makes, sense, I removed tempfile.gettempdir())
+ self.results_path = results.name
+ results.close()
+ self.test_cmd = general_test_cmd + ["--profile-path", self.profile_path,
+ "--save-path", self.results_path]
+ self.test_cmd[2] = self.test_name = test_name
vabr (Chromium) 2015/02/05 17:49:57 Are you relying on test_cmd to have a special form
dvadym 2015/02/06 11:02:26 Done. I've added comment in description of general
# TODO(rchtara): Using "timeout is just temporary until a better,
# platform-independent solution is found.
-
# The website test runs in two passes, each pass has an internal
# timeout of 200s for waiting (see |remaining_time_to_wait| and
# Wait() in websitetest.py). Accounting for some more time spent on
# the non-waiting execution, 300 seconds should be the upper bound on
# the runtime of one pass, thus 600 seconds for the whole test.
- subprocess.call(["timeout", "600"] + test_cmd)
- if os.path.isfile(results_path):
- results = open(results_path, "r")
+ self.test_cmd = ["timeout", "600"] + self.test_cmd
+ self.runner_process = None
+ # The tests can be flaky. This is why we try to rerun up to 3 times.
+ self.max_test_runs_left = 3
+ self.failures = []
+ self._run_test()
+
+ def get_test_result(self):
+ """ Return None if result is not ready yet."""
+ test_running = (self.runner_process != None and
+ self.runner_process.poll() == None)
+ if test_running: return None
+ # Test is not running, now we have to check if we want to start it again.
+ if self._check_if_test_passed():
+ print>>sys.stdout, "Test " + self.test_name + " passed"
melandory 2015/02/06 08:37:46 I would use logging instead of print, especially t
dvadym 2015/02/06 11:02:26 This is just matter of convenience for case when y
vabr (Chromium) 2015/02/06 14:32:52 In general, this kind of print statements should b
dvadym 2015/02/06 15:00:16 Ok, todo added
+ return "pass", []
+ if self.max_test_runs_left == 0:
+ print>>sys.stdout, "Test " + self.test_name + " failed"
+ return "fail", self.failures
+ self._run_test()
vabr (Chromium) 2015/02/05 17:49:58 no return?
dvadym 2015/02/06 11:02:27 In python no returns means return None, exactly as
vabr (Chromium) 2015/02/06 14:32:52 I don't like mixing the implicit return and the ex
dvadym 2015/02/06 15:00:16 Done.
+
+ def _check_if_test_passed(self):
+ if os.path.isfile(self.results_path):
+ results = open(self.results_path, "r")
count = 0 # Count the number of successful tests.
for line in results:
# TODO(melandory): We do not need to send all this data to sheet.
- failures.append(line)
+ self.failures.append(line)
count += line.count("successful='True'")
results.close()
# There is only two tests running for every website: the prompt and
# the normal test. If both of the tests were successful, the tests
# would be stopped for the current website.
+ print>>sys.stdout, "Test run of %s %s" % (self.test_name, "passed"
+ if count==2 else "failed")
if count == 2:
- return "pass", []
- else:
+ return True
+ return False
+
+ def _run_test(self):
+ try:
+ os.remove(self.results_path)
+ except Exception:
melandory 2015/02/06 08:37:47 Why don't you want to put line 162 and 166 in same
dvadym 2015/02/06 11:02:26 If the first throws exception, then the second wil
pass
- return "fail", failures
-
-
-def run_tests(
- chrome_path, chromedriver_path,
- profile_path, config_path, *args, **kwargs):
- """ Runs automated tests.
-
- Args:
- save_path: File, where results of run will be logged.
- chrome_path: Location of Chrome binary.
- chromedriver_path: Location of Chrome Driver.
- passwords_path: Location of file with credentials.
- profile_path: Location of profile path.
- *args: Variable length argument list.
- **kwargs: Arbitrary keyword arguments.
- """
+ try:
+ os.removedirs(self.profile_path)
vabr (Chromium) 2015/02/05 17:49:57 This is going to try to rmdir all parts of profile
dvadym 2015/02/06 11:02:26 Or great, thanks, it has been here for ages, I did
+ except Exception:
+ pass
+ self.max_test_runs_left -= 1
+ print>>sys.stdout, "Run of test %s started" % self.test_name
+ self.runner_process = subprocess.Popen(self.test_cmd)
+
+def run_tests(config_path):
+ """ Runs automated tests. """
environment = Environment('', '', '', None, False)
tests.Tests(environment)
config = ConfigParser.ConfigParser()
config.read(config_path)
date = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
- try:
- _, access_token = _authenticate(config.get("credentials", "pkey"),
- config.get("credentials", "client_email"))
- sheet = _spredsheeet_for_logging(config.get("drive", "key"), access_token)
- results = tempfile.NamedTemporaryFile(
- dir=os.path.join(tempfile.gettempdir()), delete=False)
- results_path = results.name
- results.close()
-
- full_path = os.path.realpath(__file__)
- tests_dir = os.path.dirname(full_path)
- tests_path = os.path.join(tests_dir, "tests.py")
-
- for websitetest in environment.websitetests:
- test_cmd = ["python", tests_path, websitetest.name,
- "--chrome-path", chrome_path,
- "--chromedriver-path", chromedriver_path,
- "--passwords-path",
- config.get("data_files", "passwords_path"),
- "--profile-path", profile_path,
- "--save-path", results_path]
- status, log = _try_run_individual_test(test_cmd, results_path)
- try:
- sheet.InsertRow(sheet.row_count,
- [websitetest.name, status, date, " | ".join(log)])
- except Exception:
- pass # TODO(melandory): Sometimes writing to spreadsheet fails. We need
- # deal with it better that just ignoring.
- finally:
- try:
- os.remove(results_path)
- except Exception:
- pass
-
+ max_tests_in_parrallel = config.getint("run_options", "tests_in_parrallel")
+ sheet_writer = SheetWriter(config)
+ full_path = os.path.realpath(__file__)
+ tests_dir = os.path.dirname(full_path)
+ tests_path = os.path.join(tests_dir, "tests.py")
+ general_test_cmd = ["python", tests_path, "test_name_placeholder",
+ "--chrome-path", config.get("binaries", "chrome-path"),
+ "--chromedriver-path", config.get("binaries", "chromedriver-path"),
+ "--passwords-path", config.get("data_files", "passwords_path")]
+ runners = []
+ tests_to_run = [test.name for test in environment.websitetests]
+ savefile = open(config.get("output", "save-path"), 'w')
+ if config.has_option("run_options", "tests_to_run"):
melandory 2015/02/06 08:37:47 I would do: if config.has_option("run_options", "t
dvadym 2015/02/06 11:02:27 I want to try to run only those tests that are cur
vabr (Chromium) 2015/02/06 14:32:52 Validation seems helpful for diagnosing problems,
dvadym 2015/02/06 15:00:16 Done.
+ user_selected_tests = config.get("run_options", "tests_to_run").split(',')
+ tests_to_run = list(set(tests_to_run) & set(user_selected_tests))
+
+ print>>sys.stdout, "Tests to run %d\nTests: %s" % (len(tests_to_run),
+ tests_to_run)
+ while len(runners) + len(tests_to_run) > 0:
vabr (Chromium) 2015/02/05 17:49:58 Isn't runners still empty here?
dvadym 2015/02/06 11:02:26 Before first iteration yes, but then they are adde
vabr (Chromium) 2015/02/06 14:32:52 Sorry, my bad. At the point when I read the end of
dvadym 2015/02/06 15:00:16 No problem :)
+ i = 0
+ while i < len(runners):
+ result = runners[i].get_test_result()
+ if result != None: # This test run is finished.
melandory 2015/02/06 08:37:46 Can't you do just "if result" here?
dvadym 2015/02/06 11:02:26 Done.
+ status, log = result
+ testinfo = [runners[i].test_name, status, date, " | ".join(log)]
+ sheet_writer.write_line_to_sheet(testinfo)
+ print>>savefile, " ".join(testinfo)
+ del runners[i]
+ else:
+ i += 1
+ while len(runners) < max_tests_in_parrallel and len(tests_to_run) > 0:
+ runners.append(TestRunner(general_test_cmd, tests_to_run.pop()))
+ time.sleep(1) # Let us wait for worker process to finish.
+ savefile.close()
melandory 2015/02/06 08:37:47 Consider using "with".
dvadym 2015/02/06 15:00:16 Done.
if __name__ == "__main__":
- parser = argparse.ArgumentParser(
- description="Password Manager automated tests runner help.")
- parser.add_argument(
- "--chrome-path", action="store", dest="chrome_path",
- help="Set the chrome path (required).", required=True)
- parser.add_argument(
- "--chromedriver-path", action="store", dest="chromedriver_path",
- help="Set the chromedriver path (required).", required=True)
- parser.add_argument(
- "--config-path", action="store", dest="config_path",
- help="File with configuration data: drive credentials, password path",
- required=True)
- parser.add_argument(
- "--profile-path", action="store", dest="profile_path",
- help="Set the profile path (required). You just need to choose a "
- "temporary empty folder. If the folder is not empty all its content "
- "is going to be removed.",
- required=True)
- args = vars(parser.parse_args())
- run_tests(**args)
+ if len(sys.argv) != 2:
+ print "Synopsis:\n python run_tests.py <config_path>"
+ config_path = sys.argv[1]
+ run_tests(config_path)

Powered by Google App Engine
This is Rietveld 408576698