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

Side by Side 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 unified diff | Download patch
OLDNEW
1 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 # Copyright 2014 The Chromium Authors. All rights reserved. 2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """This file allows the bots to be easily configure and run the tests. 6 """This file allows the bots to be easily configure and run the tests.
7 7
8 Running this script requires passing --config-path with a path to a config file 8 Running this script requires passing --config-path with a path to a config file
9 of the following structure: 9 of the following structure:
10 [credentials] 10 [data_files]
11 passwords_path=<path to a file with passwords>
12 [binaries]
13 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.
14 chromedriver-path=<chrome driver path>
15 [run_options]
16 write_to_sheet=[false|true]
17 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.
18 # |tests_to_runs| field is optional, if it is absent all tests will be run.
19 tests_to_run=<test names to run, comma delimited>
20 [output]
21 save-path=<file where to save result>
22 [sheet_info]
23 # This section is required only when write_to_sheet=true
11 pkey=full_path 24 pkey=full_path
12 client_email=email_assigned_by_google_dev_console 25 client_email=email_assigned_by_google_dev_console
13 [drive] 26 sheet_key=sheet_key_from_sheet_url
14 key=sheet_key_from_sheet_url
15 [data_files]
16 passwords_path=full_path_to_the_file_with_passwords
17 """ 27 """
18 from Sheet import Sheet
19 from apiclient.discovery import build
20 from datetime import datetime 28 from datetime import datetime
21 from gdata.gauth import OAuth2TokenFromCredentials
22 from gdata.spreadsheet.service import SpreadsheetsService
23 from oauth2client.client import SignedJwtAssertionCredentials
24 import ConfigParser 29 import ConfigParser
25 import argparse 30 import sys
26 import httplib2 31 import httplib2
27 import oauth2client.tools
28 import os 32 import os
29 import subprocess 33 import subprocess
30 import tempfile 34 import tempfile
35 import time
36 sheet_libraries_import_error = None
37 try:
38 from Sheet import Sheet
39 from apiclient.discovery import build
40 from gdata.gauth import OAuth2TokenFromCredentials
41 from gdata.spreadsheet.service import SpreadsheetsService
42 from oauth2client.client import SignedJwtAssertionCredentials
43 import oauth2client.tools
44 except ImportError as err:
45 sheet_libraries_import_error = err
46
31 47
32 from environment import Environment 48 from environment import Environment
33 import tests 49 import tests
34 50
35 _CREDENTIAL_SCOPES = "https://spreadsheets.google.com/feeds" 51 _CREDENTIAL_SCOPES = "https://spreadsheets.google.com/feeds"
36 52
37 # TODO(melandory): Function _authenticate belongs to separate module. 53 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
38 def _authenticate(pkey, client_email): 54 def __init__(self, config):
39 """ Authenticates user. 55 self.write_to_sheet = config.getboolean("run_options", "write_to_sheet")
40 56 if not self.write_to_sheet:
41 Args: 57 return
42 pkey: Full path to file with private key generated by Google 58 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
43 Developer Console. 59 raise sheet_libraries_import_error
44 client_email: Email address corresponding to private key and also 60 self.pkey = config.get("sheet_info", "pkey")
45 generated by Google Developer Console. 61 self.client_mail=config.get("sheet_info", "client_email")
46 """ 62 self.sheet_key=config.get("sheet_info", "sheet_key")
47 http, token = None, None 63 _, self.access_token = self._authenticate()
48 with open(pkey) as pkey_file: 64 self.sheet = self._spredsheeet_for_logging()
49 private_key = pkey_file.read() 65
50 credentials = SignedJwtAssertionCredentials( 66 # TODO(melandory): Function _authenticate belongs to separate module.
51 client_email, private_key, _CREDENTIAL_SCOPES) 67 def _authenticate(self):
52 http = httplib2.Http() 68 http, token = None, None
53 http = credentials.authorize(http) 69 with open(self.pkey) as pkey_file:
54 build('drive', 'v2', http=http) 70 private_key = pkey_file.read()
55 token = OAuth2TokenFromCredentials(credentials).access_token 71 credentials = SignedJwtAssertionCredentials(
56 return http, token 72 self.client_email, private_key, _CREDENTIAL_SCOPES)
57 73 http = httplib2.Http()
58 # TODO(melandory): Functionality of _spredsheeet_for_logging belongs 74 http = credentials.authorize(http)
59 # to websitetests, because this way we do not need to write results of run 75 build('drive', 'v2', http=http)
60 # in separate file and then read it here. 76 token = OAuth2TokenFromCredentials(credentials).access_token
61 def _spredsheeet_for_logging(sheet_key, access_token): 77 return http, token
62 """ Connects to document where result of test run will be logged. 78
63 Args: 79 # TODO(melandory): Functionality of _spredsheeet_for_logging belongs
64 sheet_key: Key of sheet in Trix. Can be found in sheet's URL. 80 # to websitetests, because this way we do not need to write results of run
65 access_token: Access token of an account which should have edit rights. 81 # in separate file and then read it here.
66 """ 82 def _spredsheeet_for_logging(self):
67 # Connect to trix 83 """ Connects to document where result of test run will be logged. """
68 service = SpreadsheetsService(additional_headers={ 84 # Connect to trix
69 "Authorization": "Bearer " + access_token}) 85 service = SpreadsheetsService(additional_headers={
70 sheet = Sheet(service, sheet_key) 86 "Authorization": "Bearer " + self.access_token})
71 return sheet 87 sheet = Sheet(service, self.sheet_key)
72 88 return sheet
73 def _try_run_individual_test(test_cmd, results_path): 89
74 """ Runs individual test and logs results to trix. 90 def write_line_to_sheet(self, data):
75 91 if not self.write_to_sheet:
76 Args: 92 return
77 test_cmd: String contains command which runs test.
78 results_path: Full path to file where results of test run will be logged.
79 """
80 failures = []
81 # The tests can be flaky. This is why we try to rerun up to 3 times.
82 for x in xrange(3):
83 # TODO(rchtara): Using "pkill" is just temporary until a better,
84 # platform-independent solution is found.
85 # Using any kind of kill and process name isn't best solution.
86 # Mainly because it kills every running instace of Chrome on the machine,
87 # which is unpleasant experience, when you're running tests locally.
88 # In my opinion proper solution is to invoke chrome using subproceses and
89 # then kill only this particular instance of it.
90 os.system("pkill chrome")
91 try: 93 try:
92 os.remove(results_path) 94 self.sheet.InsertRow(self.sheet.row_count, data)
93 except Exception: 95 except Exception:
94 pass 96 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.
97 # 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.
98
99 class TestRunner:
100 def __init__(self, general_test_cmd, test_name):
101 """ Args:
102 general_test_cmd: String contains part of run command common for all tests.
103 test_name: Test name (facebook for example).
104 """
105 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.
106 self.profile_path = tempfile.mkdtemp()
107 results = tempfile.NamedTemporaryFile(
108 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())
109 self.results_path = results.name
110 results.close()
111 self.test_cmd = general_test_cmd + ["--profile-path", self.profile_path,
112 "--save-path", self.results_path]
113 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
95 # TODO(rchtara): Using "timeout is just temporary until a better, 114 # TODO(rchtara): Using "timeout is just temporary until a better,
96 # platform-independent solution is found. 115 # platform-independent solution is found.
97
98 # The website test runs in two passes, each pass has an internal 116 # The website test runs in two passes, each pass has an internal
99 # timeout of 200s for waiting (see |remaining_time_to_wait| and 117 # timeout of 200s for waiting (see |remaining_time_to_wait| and
100 # Wait() in websitetest.py). Accounting for some more time spent on 118 # Wait() in websitetest.py). Accounting for some more time spent on
101 # the non-waiting execution, 300 seconds should be the upper bound on 119 # the non-waiting execution, 300 seconds should be the upper bound on
102 # the runtime of one pass, thus 600 seconds for the whole test. 120 # the runtime of one pass, thus 600 seconds for the whole test.
103 subprocess.call(["timeout", "600"] + test_cmd) 121 self.test_cmd = ["timeout", "600"] + self.test_cmd
104 if os.path.isfile(results_path): 122 self.runner_process = None
105 results = open(results_path, "r") 123 # The tests can be flaky. This is why we try to rerun up to 3 times.
124 self.max_test_runs_left = 3
125 self.failures = []
126 self._run_test()
127
128 def get_test_result(self):
129 """ Return None if result is not ready yet."""
130 test_running = (self.runner_process != None and
131 self.runner_process.poll() == None)
132 if test_running: return None
133 # Test is not running, now we have to check if we want to start it again.
134 if self._check_if_test_passed():
135 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
136 return "pass", []
137 if self.max_test_runs_left == 0:
138 print>>sys.stdout, "Test " + self.test_name + " failed"
139 return "fail", self.failures
140 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.
141
142 def _check_if_test_passed(self):
143 if os.path.isfile(self.results_path):
144 results = open(self.results_path, "r")
106 count = 0 # Count the number of successful tests. 145 count = 0 # Count the number of successful tests.
107 for line in results: 146 for line in results:
108 # TODO(melandory): We do not need to send all this data to sheet. 147 # TODO(melandory): We do not need to send all this data to sheet.
109 failures.append(line) 148 self.failures.append(line)
110 count += line.count("successful='True'") 149 count += line.count("successful='True'")
111 results.close() 150 results.close()
112 # There is only two tests running for every website: the prompt and 151 # There is only two tests running for every website: the prompt and
113 # the normal test. If both of the tests were successful, the tests 152 # the normal test. If both of the tests were successful, the tests
114 # would be stopped for the current website. 153 # would be stopped for the current website.
154 print>>sys.stdout, "Test run of %s %s" % (self.test_name, "passed"
155 if count==2 else "failed")
115 if count == 2: 156 if count == 2:
116 return "pass", [] 157 return True
117 else: 158 return False
159
160 def _run_test(self):
161 try:
162 os.remove(self.results_path)
163 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
118 pass 164 pass
119 return "fail", failures 165 try:
120 166 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
121 167 except Exception:
122 def run_tests( 168 pass
123 chrome_path, chromedriver_path, 169 self.max_test_runs_left -= 1
124 profile_path, config_path, *args, **kwargs): 170 print>>sys.stdout, "Run of test %s started" % self.test_name
125 """ Runs automated tests. 171 self.runner_process = subprocess.Popen(self.test_cmd)
126 172
127 Args: 173 def run_tests(config_path):
128 save_path: File, where results of run will be logged. 174 """ Runs automated tests. """
129 chrome_path: Location of Chrome binary.
130 chromedriver_path: Location of Chrome Driver.
131 passwords_path: Location of file with credentials.
132 profile_path: Location of profile path.
133 *args: Variable length argument list.
134 **kwargs: Arbitrary keyword arguments.
135 """
136 environment = Environment('', '', '', None, False) 175 environment = Environment('', '', '', None, False)
137 tests.Tests(environment) 176 tests.Tests(environment)
138 config = ConfigParser.ConfigParser() 177 config = ConfigParser.ConfigParser()
139 config.read(config_path) 178 config.read(config_path)
140 date = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') 179 date = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
141 try: 180 max_tests_in_parrallel = config.getint("run_options", "tests_in_parrallel")
142 _, access_token = _authenticate(config.get("credentials", "pkey"), 181 sheet_writer = SheetWriter(config)
143 config.get("credentials", "client_email")) 182 full_path = os.path.realpath(__file__)
144 sheet = _spredsheeet_for_logging(config.get("drive", "key"), access_token) 183 tests_dir = os.path.dirname(full_path)
145 results = tempfile.NamedTemporaryFile( 184 tests_path = os.path.join(tests_dir, "tests.py")
146 dir=os.path.join(tempfile.gettempdir()), delete=False) 185 general_test_cmd = ["python", tests_path, "test_name_placeholder",
147 results_path = results.name 186 "--chrome-path", config.get("binaries", "chrome-path"),
148 results.close() 187 "--chromedriver-path", config.get("binaries", "chromedriver-path"),
149 188 "--passwords-path", config.get("data_files", "passwords_path")]
150 full_path = os.path.realpath(__file__) 189 runners = []
151 tests_dir = os.path.dirname(full_path) 190 tests_to_run = [test.name for test in environment.websitetests]
152 tests_path = os.path.join(tests_dir, "tests.py") 191 savefile = open(config.get("output", "save-path"), 'w')
153 192 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.
154 for websitetest in environment.websitetests: 193 user_selected_tests = config.get("run_options", "tests_to_run").split(',')
155 test_cmd = ["python", tests_path, websitetest.name, 194 tests_to_run = list(set(tests_to_run) & set(user_selected_tests))
156 "--chrome-path", chrome_path, 195
157 "--chromedriver-path", chromedriver_path, 196 print>>sys.stdout, "Tests to run %d\nTests: %s" % (len(tests_to_run),
158 "--passwords-path", 197 tests_to_run)
159 config.get("data_files", "passwords_path"), 198 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 :)
160 "--profile-path", profile_path, 199 i = 0
161 "--save-path", results_path] 200 while i < len(runners):
162 status, log = _try_run_individual_test(test_cmd, results_path) 201 result = runners[i].get_test_result()
163 try: 202 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.
164 sheet.InsertRow(sheet.row_count, 203 status, log = result
165 [websitetest.name, status, date, " | ".join(log)]) 204 testinfo = [runners[i].test_name, status, date, " | ".join(log)]
166 except Exception: 205 sheet_writer.write_line_to_sheet(testinfo)
167 pass # TODO(melandory): Sometimes writing to spreadsheet fails. We need 206 print>>savefile, " ".join(testinfo)
168 # deal with it better that just ignoring. 207 del runners[i]
169 finally: 208 else:
170 try: 209 i += 1
171 os.remove(results_path) 210 while len(runners) < max_tests_in_parrallel and len(tests_to_run) > 0:
172 except Exception: 211 runners.append(TestRunner(general_test_cmd, tests_to_run.pop()))
173 pass 212 time.sleep(1) # Let us wait for worker process to finish.
174 213 savefile.close()
melandory 2015/02/06 08:37:47 Consider using "with".
dvadym 2015/02/06 15:00:16 Done.
175 214
176 if __name__ == "__main__": 215 if __name__ == "__main__":
177 parser = argparse.ArgumentParser( 216 if len(sys.argv) != 2:
178 description="Password Manager automated tests runner help.") 217 print "Synopsis:\n python run_tests.py <config_path>"
179 parser.add_argument( 218 config_path = sys.argv[1]
180 "--chrome-path", action="store", dest="chrome_path", 219 run_tests(config_path)
181 help="Set the chrome path (required).", required=True)
182 parser.add_argument(
183 "--chromedriver-path", action="store", dest="chromedriver_path",
184 help="Set the chromedriver path (required).", required=True)
185 parser.add_argument(
186 "--config-path", action="store", dest="config_path",
187 help="File with configuration data: drive credentials, password path",
188 required=True)
189 parser.add_argument(
190 "--profile-path", action="store", dest="profile_path",
191 help="Set the profile path (required). You just need to choose a "
192 "temporary empty folder. If the folder is not empty all its content "
193 "is going to be removed.",
194 required=True)
195 args = vars(parser.parse_args())
196 run_tests(**args)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698