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

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: Fix comment 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
« no previous file with comments | « components/test/data/password_manager/automated_tests/environment.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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>
14 chromedriver-path=<chrome driver path>
15 [run_options]
16 write_to_sheet=[false|true]
17 tests_in_parrallel=<number of parallel tests>
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
33 import shutil
29 import subprocess 34 import subprocess
30 import tempfile 35 import tempfile
36 import time
37 sheet_libraries_import_error = None
38 try:
39 from Sheet import Sheet
40 from apiclient.discovery import build
41 from gdata.gauth import OAuth2TokenFromCredentials
42 from gdata.spreadsheet.service import SpreadsheetsService
43 from oauth2client.client import SignedJwtAssertionCredentials
44 import oauth2client.tools
45 except ImportError as err:
46 sheet_libraries_import_error = err
47
31 48
32 from environment import Environment 49 from environment import Environment
33 import tests 50 import tests
34 51
35 _CREDENTIAL_SCOPES = "https://spreadsheets.google.com/feeds" 52 _CREDENTIAL_SCOPES = "https://spreadsheets.google.com/feeds"
36 53
37 # TODO(melandory): Function _authenticate belongs to separate module. 54 # TODO(dvadym) Change all prints in this file to correspond logging.
38 def _authenticate(pkey, client_email): 55
39 """ Authenticates user. 56 # TODO(dvadym) Consider to move this class to separate file.
40 57 class SheetWriter(object):
41 Args: 58
42 pkey: Full path to file with private key generated by Google 59 def __init__(self, config):
43 Developer Console. 60 self.write_to_sheet = config.getboolean("run_options", "write_to_sheet")
44 client_email: Email address corresponding to private key and also 61 if not self.write_to_sheet:
45 generated by Google Developer Console. 62 return
46 """ 63 if sheet_libraries_import_error:
47 http, token = None, None 64 raise sheet_libraries_import_error
48 with open(pkey) as pkey_file: 65 self.pkey = config.get("sheet_info", "pkey")
49 private_key = pkey_file.read() 66 self.client_mail = config.get("sheet_info", "client_email")
50 credentials = SignedJwtAssertionCredentials( 67 self.sheet_key = config.get("sheet_info", "sheet_key")
51 client_email, private_key, _CREDENTIAL_SCOPES) 68 _, self.access_token = self._authenticate()
52 http = httplib2.Http() 69 self.sheet = self._spredsheeet_for_logging()
53 http = credentials.authorize(http) 70
54 build('drive', 'v2', http=http) 71 # TODO(melandory): Function _authenticate belongs to separate module.
55 token = OAuth2TokenFromCredentials(credentials).access_token 72 def _authenticate(self):
56 return http, token 73 http, token = None, None
57 74 with open(self.pkey) as pkey_file:
58 # TODO(melandory): Functionality of _spredsheeet_for_logging belongs 75 private_key = pkey_file.read()
59 # to websitetests, because this way we do not need to write results of run 76 credentials = SignedJwtAssertionCredentials(
60 # in separate file and then read it here. 77 self.client_email, private_key, _CREDENTIAL_SCOPES)
61 def _spredsheeet_for_logging(sheet_key, access_token): 78 http = httplib2.Http()
62 """ Connects to document where result of test run will be logged. 79 http = credentials.authorize(http)
63 Args: 80 build("drive", "v2", http=http)
64 sheet_key: Key of sheet in Trix. Can be found in sheet's URL. 81 token = OAuth2TokenFromCredentials(credentials).access_token
65 access_token: Access token of an account which should have edit rights. 82 return http, token
66 """ 83
67 # Connect to trix 84 # TODO(melandory): Functionality of _spredsheeet_for_logging belongs
68 service = SpreadsheetsService(additional_headers={ 85 # to websitetests, because this way we do not need to write results of run
69 "Authorization": "Bearer " + access_token}) 86 # in separate file and then read it here.
70 sheet = Sheet(service, sheet_key) 87 def _spredsheeet_for_logging(self):
71 return sheet 88 """ Connects to document where result of test run will be logged. """
72 89 # Connect to trix
73 def _try_run_individual_test(test_cmd, results_path): 90 service = SpreadsheetsService(additional_headers={
74 """ Runs individual test and logs results to trix. 91 "Authorization": "Bearer " + self.access_token})
75 92 sheet = Sheet(service, self.sheet_key)
76 Args: 93 return sheet
77 test_cmd: String contains command which runs test. 94
78 results_path: Full path to file where results of test run will be logged. 95 def write_line_to_sheet(self, data):
79 """ 96 if not self.write_to_sheet:
80 failures = [] 97 return
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: 98 try:
92 os.remove(results_path) 99 self.sheet.InsertRow(self.sheet.row_count, data)
93 except Exception: 100 except Exception:
94 pass 101 pass # TODO(melandory): Sometimes writing to spreadsheet fails. We need
102 # to deal with it better that just ignoring it.
103
104 class TestRunner(object):
105
106 def __init__(self, general_test_cmd, test_name):
107 """ Args:
108 general_test_cmd: String contains part of run command common for all tests,
109 [2] is placeholder for test name.
110 test_name: Test name (facebook for example).
111 """
112 self.profile_path = tempfile.mkdtemp()
113 results = tempfile.NamedTemporaryFile(delete=False)
114 self.results_path = results.name
115 results.close()
116 self.test_cmd = general_test_cmd + ["--profile-path", self.profile_path,
117 "--save-path", self.results_path]
118 self.test_cmd[2] = self.test_name = test_name
95 # TODO(rchtara): Using "timeout is just temporary until a better, 119 # TODO(rchtara): Using "timeout is just temporary until a better,
96 # platform-independent solution is found. 120 # platform-independent solution is found.
97
98 # The website test runs in two passes, each pass has an internal 121 # The website test runs in two passes, each pass has an internal
99 # timeout of 200s for waiting (see |remaining_time_to_wait| and 122 # timeout of 200s for waiting (see |remaining_time_to_wait| and
100 # Wait() in websitetest.py). Accounting for some more time spent on 123 # Wait() in websitetest.py). Accounting for some more time spent on
101 # the non-waiting execution, 300 seconds should be the upper bound on 124 # 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. 125 # the runtime of one pass, thus 600 seconds for the whole test.
103 subprocess.call(["timeout", "600"] + test_cmd) 126 self.test_cmd = ["timeout", "600"] + self.test_cmd
104 if os.path.isfile(results_path): 127 self.runner_process = None
105 results = open(results_path, "r") 128 # The tests can be flaky. This is why we try to rerun up to 3 times.
106 count = 0 # Count the number of successful tests. 129 self.max_test_runs_left = 3
130 self.failures = []
131 self._run_test()
132
133 def get_test_result(self):
134 """ Return None if result is not ready yet."""
135 test_running = self.runner_process and self.runner_process.poll() is None
136 if test_running: return None
137 # Test is not running, now we have to check if we want to start it again.
138 if self._check_if_test_passed():
139 print "Test " + self.test_name + " passed"
140 return "pass", []
141 if self.max_test_runs_left == 0:
142 print "Test " + self.test_name + " failed"
143 return "fail", self.failures
144 self._run_test()
145 return None
146
147 def _check_if_test_passed(self):
148 if os.path.isfile(self.results_path):
149 results = open(self.results_path, "r")
150 count = 0 # Count the number of successful tests.
107 for line in results: 151 for line in results:
108 # TODO(melandory): We do not need to send all this data to sheet. 152 # TODO(melandory): We do not need to send all this data to sheet.
109 failures.append(line) 153 self.failures.append(line)
110 count += line.count("successful='True'") 154 count += line.count("successful='True'")
111 results.close() 155 results.close()
112 # There is only two tests running for every website: the prompt and 156 # 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 157 # the normal test. If both of the tests were successful, the tests
114 # would be stopped for the current website. 158 # would be stopped for the current website.
159 print "Test run of %s %s" % (self.test_name, "passed"
160 if count == 2 else "failed")
115 if count == 2: 161 if count == 2:
116 return "pass", [] 162 return True
117 else: 163 return False
164
165 def _run_test(self):
166 """Run separate process that once run test for one site."""
167 try:
168 os.remove(self.results_path)
169 except Exception:
118 pass 170 pass
119 return "fail", failures 171 try:
120 172 shutil.rmtree(self.profile_path)
121 173 except Exception:
122 def run_tests( 174 pass
123 chrome_path, chromedriver_path, 175 self.max_test_runs_left -= 1
124 profile_path, config_path, *args, **kwargs): 176 print "Run of test %s started" % self.test_name
125 """ Runs automated tests. 177 self.runner_process = subprocess.Popen(self.test_cmd)
126 178
127 Args: 179 def run_tests(config_path):
128 save_path: File, where results of run will be logged. 180 """ Runs automated tests. """
129 chrome_path: Location of Chrome binary. 181 environment = Environment("", "", "", None, False)
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)
137 tests.Tests(environment) 182 tests.Tests(environment)
138 config = ConfigParser.ConfigParser() 183 config = ConfigParser.ConfigParser()
139 config.read(config_path) 184 config.read(config_path)
140 date = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') 185 date = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
141 try: 186 max_tests_in_parrallel = config.getint("run_options", "tests_in_parrallel")
142 _, access_token = _authenticate(config.get("credentials", "pkey"), 187 sheet_writer = SheetWriter(config)
143 config.get("credentials", "client_email")) 188 full_path = os.path.realpath(__file__)
144 sheet = _spredsheeet_for_logging(config.get("drive", "key"), access_token) 189 tests_dir = os.path.dirname(full_path)
145 results = tempfile.NamedTemporaryFile( 190 tests_path = os.path.join(tests_dir, "tests.py")
146 dir=os.path.join(tempfile.gettempdir()), delete=False) 191 general_test_cmd = ["python", tests_path, "test_name_placeholder",
147 results_path = results.name 192 "--chrome-path", config.get("binaries", "chrome-path"),
148 results.close() 193 "--chromedriver-path", config.get("binaries", "chromedriver-path"),
149 194 "--passwords-path", config.get("data_files", "passwords_path")]
150 full_path = os.path.realpath(__file__) 195 runners = []
151 tests_dir = os.path.dirname(full_path) 196 tests_to_run = [test.name for test in environment.websitetests]
152 tests_path = os.path.join(tests_dir, "tests.py") 197 if config.has_option("run_options", "tests_to_run"):
153 198 user_selected_tests = config.get("run_options", "tests_to_run").split(',')
154 for websitetest in environment.websitetests: 199 # TODO((dvadym) Validate the user selected tests are available.
155 test_cmd = ["python", tests_path, websitetest.name, 200 tests_to_run = list(set(tests_to_run) & set(user_selected_tests))
156 "--chrome-path", chrome_path, 201
157 "--chromedriver-path", chromedriver_path, 202 with open(config.get("output", "save-path"), 'w') as savefile:
158 "--passwords-path", 203 print "Tests to run %d\nTests: %s" % (len(tests_to_run), tests_to_run)
159 config.get("data_files", "passwords_path"), 204 while len(runners) + len(tests_to_run) > 0:
160 "--profile-path", profile_path, 205 i = 0
161 "--save-path", results_path] 206 while i < len(runners):
162 status, log = _try_run_individual_test(test_cmd, results_path) 207 result = runners[i].get_test_result()
163 try: 208 if result: # This test run is finished.
164 sheet.InsertRow(sheet.row_count, 209 status, log = result
165 [websitetest.name, status, date, " | ".join(log)]) 210 testinfo = [runners[i].test_name, status, date, " | ".join(log)]
166 except Exception: 211 sheet_writer.write_line_to_sheet(testinfo)
167 pass # TODO(melandory): Sometimes writing to spreadsheet fails. We need 212 print>>savefile, " ".join(testinfo)
168 # deal with it better that just ignoring. 213 del runners[i]
169 finally: 214 else:
170 try: 215 i += 1
171 os.remove(results_path) 216 while len(runners) < max_tests_in_parrallel and len(tests_to_run) > 0:
172 except Exception: 217 runners.append(TestRunner(general_test_cmd, tests_to_run.pop()))
173 pass 218 time.sleep(1) # Let us wait for worker process to finish.
174
175 219
176 if __name__ == "__main__": 220 if __name__ == "__main__":
177 parser = argparse.ArgumentParser( 221 if len(sys.argv) != 2:
178 description="Password Manager automated tests runner help.") 222 print "Synopsis:\n python run_tests.py <config_path>"
179 parser.add_argument( 223 config_path = sys.argv[1]
180 "--chrome-path", action="store", dest="chrome_path", 224 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
« no previous file with comments | « components/test/data/password_manager/automated_tests/environment.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698