OLD | NEW |
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 |
| 9 of the following structure: |
| 10 [credentials] |
| 11 pkey=full_path |
| 12 client_email=email_assigned_by_google_dev_console |
| 13 [drive] |
| 14 key=sheet_key_from_sheet_url |
| 15 [data_files] |
| 16 passwords_path=full_path_to_the_file_with_passwords |
| 17 """ |
| 18 from Sheet import Sheet |
| 19 from apiclient.discovery import build |
| 20 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 |
8 import argparse | 25 import argparse |
| 26 import httplib2 |
| 27 import oauth2client.tools |
9 import os | 28 import os |
10 import tempfile | 29 import tempfile |
11 | 30 |
12 from environment import Environment | 31 from environment import Environment |
13 import tests | 32 import tests |
14 | 33 |
| 34 _CREDENTIAL_SCOPES = "https://spreadsheets.google.com/feeds" |
| 35 |
| 36 # TODO(melandory): Function _authenticate belongs to separate module. |
| 37 def _authenticate(pkey, client_email): |
| 38 """ Authenticates user. |
| 39 |
| 40 Args: |
| 41 pkey: Full path to file with private key generated by Google |
| 42 Developer Console. |
| 43 client_email: Email address corresponding to private key and also |
| 44 generated by Google Developer Console. |
| 45 """ |
| 46 http, token = None, None |
| 47 with open(pkey) as pkey_file: |
| 48 private_key = pkey_file.read() |
| 49 credentials = SignedJwtAssertionCredentials( |
| 50 client_email, private_key, _CREDENTIAL_SCOPES) |
| 51 http = httplib2.Http() |
| 52 http = credentials.authorize(http) |
| 53 build('drive', 'v2', http=http) |
| 54 token = OAuth2TokenFromCredentials(credentials).access_token |
| 55 return http, token |
| 56 |
| 57 # TODO(melandory): Functionality of _spredsheeet_for_logging belongs |
| 58 # to websitetests, because this way we do not need to write results of run |
| 59 # in separate file and then read it here. |
| 60 def _spredsheeet_for_logging(sheet_key, access_token): |
| 61 """ Connects to document where result of test run will be logged. |
| 62 Args: |
| 63 sheet_key: Key of sheet in Trix. Can be found in sheet's URL. |
| 64 access_token: Access token of an account which should have edit rights. |
| 65 """ |
| 66 # Connect to trix |
| 67 service = SpreadsheetsService(additional_headers={ |
| 68 "Authorization": "Bearer " + access_token}) |
| 69 sheet = Sheet(service, sheet_key) |
| 70 return sheet |
| 71 |
| 72 def _try_run_individual_test(test_cmd, results_path): |
| 73 """ Runs individual test and logs results to trix. |
| 74 |
| 75 Args: |
| 76 test_cmd: String contains command which runs test. |
| 77 results_path: Full path to file where results of test run will be logged. |
| 78 """ |
| 79 failures = [] |
| 80 # The tests can be flaky. This is why we try to rerun up to 3 times. |
| 81 for x in xrange(3): |
| 82 # TODO(rchtara): Using "pkill" is just temporary until a better, |
| 83 # platform-independent solution is found. |
| 84 # Using any kind of kill and process name isn't best solution. |
| 85 # Mainly because it kills every running instace of Chrome on the machine, |
| 86 # which is unpleasant experience, when you're running tests locally. |
| 87 # In my opinion proper solution is to invoke chrome using subproceses and |
| 88 # then kill only this particular instance of it. |
| 89 os.system("pkill chrome") |
| 90 try: |
| 91 os.remove(results_path) |
| 92 except Exception: |
| 93 pass |
| 94 # TODO(rchtara): Using "timeout is just temporary until a better, |
| 95 # platform-independent solution is found. |
| 96 |
| 97 # The website test runs in two passes, each pass has an internal |
| 98 # timeout of 200s for waiting (see |remaining_time_to_wait| and |
| 99 # Wait() in websitetest.py). Accounting for some more time spent on |
| 100 # the non-waiting execution, 300 seconds should be the upper bound on |
| 101 # the runtime of one pass, thus 600 seconds for the whole test. |
| 102 # TODO(vabr): Use subprocess.call. |
| 103 os.system("timeout 600 %s" % test_cmd) |
| 104 if os.path.isfile(results_path): |
| 105 results = open(results_path, "r") |
| 106 count = 0 # Count the number of successful tests. |
| 107 for line in results: |
| 108 # TODO(melandory): We do not need to send all this data to sheet. |
| 109 failures.append(line) |
| 110 count += line.count("successful='True'") |
| 111 results.close() |
| 112 # 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 |
| 114 # would be stopped for the current website. |
| 115 if count == 2: |
| 116 return "pass", [] |
| 117 else: |
| 118 pass |
| 119 return "fail", failures |
| 120 |
| 121 |
15 def run_tests( | 122 def run_tests( |
16 save_path, chrome_path, chromedriver_path, | 123 chrome_path, chromedriver_path, |
17 passwords_path, profile_path, *args, **kwargs): | 124 profile_path, config_path, *args, **kwargs): |
18 """ Runs automated tests. | 125 """ Runs automated tests. |
19 | 126 |
20 Args: | 127 Args: |
21 save_path: File, where results of run will be logged. | 128 save_path: File, where results of run will be logged. |
22 chrome_path: Location of Chrome binary. | 129 chrome_path: Location of Chrome binary. |
23 chromedriver_path: Location of Chrome Driver. | 130 chromedriver_path: Location of Chrome Driver. |
24 passwords_path: Location of file with credentials. | 131 passwords_path: Location of file with credentials. |
25 profile_path: Location of profile path. | 132 profile_path: Location of profile path. |
26 *args: Variable length argument list. | 133 *args: Variable length argument list. |
27 **kwargs: Arbitrary keyword arguments. | 134 **kwargs: Arbitrary keyword arguments. |
28 """ | 135 """ |
29 environment = Environment('', '', '', None, False) | 136 environment = Environment('', '', '', None, False) |
30 tests.Tests(environment) | 137 tests.Tests(environment) |
31 | 138 config = ConfigParser.ConfigParser() |
32 xml = open(save_path, "w") | 139 config.read(config_path) |
33 xml.write("<xml>") | 140 date = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') |
34 try: | 141 try: |
| 142 _, access_token = _authenticate(config.get("credentials", "pkey"), |
| 143 config.get("credentials", "client_email")) |
| 144 sheet = _spredsheeet_for_logging(config.get("drive", "key"), access_token) |
35 results = tempfile.NamedTemporaryFile( | 145 results = tempfile.NamedTemporaryFile( |
36 dir=os.path.join(tempfile.gettempdir()), delete=False) | 146 dir=os.path.join(tempfile.gettempdir()), delete=False) |
37 results_path = results.name | 147 results_path = results.name |
38 results.close() | 148 results.close() |
39 | 149 |
40 full_path = os.path.realpath(__file__) | 150 full_path = os.path.realpath(__file__) |
41 tests_dir = os.path.dirname(full_path) | 151 tests_dir = os.path.dirname(full_path) |
42 tests_path = os.path.join(tests_dir, "tests.py") | 152 tests_path = os.path.join(tests_dir, "tests.py") |
43 | 153 |
44 for websitetest in environment.websitetests: | 154 for websitetest in environment.websitetests: |
45 # The tests can be flaky. This is why we try to rerun up to 3 times. | 155 test_cmd = ("python %s %s --chrome-path %s " |
46 for x in range(0, 3): | 156 "--chromedriver-path %s" |
47 # TODO(rchtara): Using "pkill" is just temporary until a better, | 157 "--passwords-path %s --profile-path %s " |
48 # platform-independent solution is found. | 158 "--save-path %s" % |
49 os.system("pkill chrome") | 159 (tests_path, websitetest.name, chrome_path, |
50 try: | 160 chromedriver_path, |
51 os.remove(results_path) | 161 config.get("data_files", "passwords_path"), |
52 except Exception: | 162 profile_path, results_path)) |
53 pass | 163 status, log = _try_run_individual_test( |
54 # TODO(rchtara): Using "timeout is just temporary until a better, | 164 websitetest, test_cmd, results_path) |
55 # platform-independent solution is found. | 165 try: |
56 | 166 sheet.InsertRow(sheet.row_count, |
57 # The website test runs in two passes, each pass has an internal | 167 [websitetest.name, status, date, " | ".join(log)]) |
58 # timeout of 200s for waiting (see |remaining_time_to_wait| and | 168 except Exception: |
59 # Wait() in websitetest.py). Accounting for some more time spent on | 169 pass # TODO(melandory): Sometimes writing to spreadsheet fails. We need |
60 # the non-waiting execution, 300 seconds should be the upper bound on | 170 # deal with it better that just ignoring. |
61 # the runtime of one pass, thus 600 seconds for the whole test. | |
62 os.system("timeout 600 python %s %s --chrome-path %s " | |
63 "--chromedriver-path %s --passwords-path %s --profile-path %s " | |
64 "--save-path %s" % | |
65 (tests_path, websitetest.name, chrome_path, | |
66 chromedriver_path, passwords_path, | |
67 profile_path, results_path)) | |
68 if os.path.isfile(results_path): | |
69 results = open(results_path, "r") | |
70 count = 0 # Count the number of successful tests. | |
71 for line in results: | |
72 xml.write(line) | |
73 count += line.count("successful='True'") | |
74 results.close() | |
75 # There is only two tests running for every website: the prompt and | |
76 # the normal test. If both of the tests were successful, the tests | |
77 # would be stopped for the current website. | |
78 if count == 2: | |
79 break | |
80 else: | |
81 xml.write("<result><test name='%s' type='prompt' successful='false'>" | |
82 "</test><test name='%s' type='normal' successful='false'></test>" | |
83 "</result>" % (websitetest.name, websitetest.name)) | |
84 finally: | 171 finally: |
85 try: | 172 try: |
86 os.remove(results_path) | 173 os.remove(results_path) |
87 except Exception: | 174 except Exception: |
88 pass | 175 pass |
89 | 176 |
90 xml.write("</xml>") | |
91 xml.close() | |
92 | 177 |
93 if __name__ == "__main__": | 178 if __name__ == "__main__": |
94 parser = argparse.ArgumentParser( | 179 parser = argparse.ArgumentParser( |
95 description="Password Manager automated tests runner help.") | 180 description="Password Manager automated tests runner help.") |
96 parser.add_argument( | 181 parser.add_argument( |
97 "--chrome-path", action="store", dest="chrome_path", | 182 "--chrome-path", action="store", dest="chrome_path", |
98 help="Set the chrome path (required).", required=True) | 183 help="Set the chrome path (required).", required=True) |
99 parser.add_argument( | 184 parser.add_argument( |
100 "--chromedriver-path", action="store", dest="chromedriver_path", | 185 "--chromedriver-path", action="store", dest="chromedriver_path", |
101 help="Set the chromedriver path (required).", required=True) | 186 help="Set the chromedriver path (required).", required=True) |
102 parser.add_argument( | 187 parser.add_argument( |
| 188 "--config-path", action="store", dest="config_path", |
| 189 help="File with configuration data: drive credentials, password path", |
| 190 required=True) |
| 191 parser.add_argument( |
103 "--profile-path", action="store", dest="profile_path", | 192 "--profile-path", action="store", dest="profile_path", |
104 help="Set the profile path (required). You just need to choose a " | 193 help="Set the profile path (required). You just need to choose a " |
105 "temporary empty folder. If the folder is not empty all its content " | 194 "temporary empty folder. If the folder is not empty all its content " |
106 "is going to be removed.", | 195 "is going to be removed.", |
107 required=True) | 196 required=True) |
108 parser.add_argument( | |
109 "--passwords-path", action="store", dest="passwords_path", | |
110 help="Set the usernames/passwords path (required).", required=True) | |
111 parser.add_argument("--save-path", action="store", dest="save_path", | |
112 help="Write the results in a file.", required=True) | |
113 args = vars(parser.parse_args()) | 197 args = vars(parser.parse_args()) |
114 run_tests(**args) | 198 run_tests(**args) |
OLD | NEW |