Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Tools / TestResultServer / handlers / testfilehandler.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 #     * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 #     * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
12 # distribution.
13 #     * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 import time
30 import logging
31 import re
32 import urllib
33 import webapp2
34
35 from google.appengine.api import users
36 from google.appengine.ext.webapp import template
37 from google.appengine.ext import db
38
39 import master_config
40
41 from model.jsonresults import JsonResults
42 from model.testfile import TestFile
43
44 PARAM_MASTER = "master"
45 PARAM_BUILDER = "builder"
46 PARAM_BUILD_NUMBER = "buildnumber"
47 PARAM_DIR = "dir"
48 PARAM_FILE = "file"
49 PARAM_NAME = "name"
50 PARAM_BEFORE = "before"
51 PARAM_NUM_FILES = "numfiles"
52 PARAM_KEY = "key"
53 PARAM_TEST_TYPE = "testtype"
54 PARAM_TEST_LIST_JSON = "testlistjson"
55 PARAM_CALLBACK = "callback"
56
57
58 def _replace_jsonp_callback(json, callback_name):
59     if callback_name and re.search(r"^[A-Za-z0-9_]+$", callback_name):
60         if re.search(r"^[A-Za-z0-9_]+[(]", json):
61             return re.sub(r"^[A-Za-z0-9_]+[(]", callback_name + "(", json)
62         return callback_name + "(" + json + ")"
63
64     return json
65
66
67 class DeleteFile(webapp2.RequestHandler):
68     """Delete test file for a given builder and name from datastore."""
69
70     def get(self):
71         key = self.request.get(PARAM_KEY)
72         # Intentionally don't munge the master from deprecated names here.
73         # Assume anyone deleting files wants explicit control.
74         master = self.request.get(PARAM_MASTER)
75         builder = self.request.get(PARAM_BUILDER)
76         test_type = self.request.get(PARAM_TEST_TYPE)
77         build_number = self.request.get(PARAM_BUILD_NUMBER, default_value=None)
78         name = self.request.get(PARAM_NAME)
79         num_files = self.request.get(PARAM_NUM_FILES)
80         before = self.request.get(PARAM_BEFORE)
81
82         logging.debug(
83             "Deleting File, master: %s, builder: %s, test_type: %s, build_number: %s, name: %s, before: %s, key: %s.",
84             master, builder, test_type, build_number, name, before, key)
85
86         limit = int(num_files) if num_files else 1
87         num_deleted = TestFile.delete_file(key, master, builder, test_type, build_number, name, before, limit)
88
89         self.response.set_status(200)
90         self.response.out.write("Deleted %d files." % num_deleted)
91
92
93 class GetFile(webapp2.RequestHandler):
94     """Get file content or list of files for given builder and name."""
95
96     def _get_file_list(self, master, builder, test_type, build_number, name, before, limit, callback_name=None):
97         """Get and display a list of files that matches builder and file name.
98
99         Args:
100             builder: builder name
101             test_type: type of the test
102             name: file name
103         """
104
105         files = TestFile.get_files(
106             master, builder, test_type, build_number, name, before, load_data=False, limit=limit)
107         if not files:
108             logging.info("File not found, master: %s, builder: %s, test_type: %s, build_number: %s, name: %s.",
109                          master, builder, test_type, build_number, name)
110             self.response.out.write("File not found")
111             return
112
113         template_values = {
114             "admin": users.is_current_user_admin(),
115             "master": master,
116             "builder": builder,
117             "test_type": test_type,
118             "build_number": build_number,
119             "name": name,
120             "files": files,
121         }
122         if callback_name:
123             json = template.render("templates/showfilelist.jsonp", template_values)
124             self._serve_json(_replace_jsonp_callback(json, callback_name), files[0].date)
125             return
126         self.response.out.write(template.render("templates/showfilelist.html",
127                                                 template_values))
128
129     def _get_file_content(self, master, builder, test_type, build_number, name):
130         """Return content of the file that matches builder and file name.
131
132         Args:
133             builder: builder name
134             test_type: type of the test
135             build_number: build number, or 'latest'
136             name: file name
137         """
138
139         files = TestFile.get_files(
140             master, builder, test_type, build_number, name, load_data=True, limit=1)
141         if not files:
142             logging.info("File not found, master %s, builder: %s, test_type: %s, build_number: %s, name: %s.",
143                          master, builder, test_type, build_number, name)
144             return None, None
145
146         return files[0].data, files[0].date
147
148     def _get_file_content_from_key(self, key):
149         file = db.get(key)
150
151         if not file:
152             logging.info("File not found, key %s.", key)
153             return None
154
155         file.load_data()
156         return file.data, file.date
157
158     def _serve_json(self, json, modified_date):
159         if json:
160             if "If-Modified-Since" in self.request.headers:
161                 old_date = self.request.headers["If-Modified-Since"]
162                 if time.strptime(old_date, '%a, %d %b %Y %H:%M:%S %Z') == modified_date.utctimetuple():
163                     self.response.set_status(304)
164                     return
165
166             # The appengine datetime objects are naive, so they lack a timezone.
167             # In practice, appengine seems to use GMT.
168             self.response.headers["Last-Modified"] = modified_date.strftime('%a, %d %b %Y %H:%M:%S') + ' GMT'
169             self.response.headers["Content-Type"] = "application/json"
170             self.response.headers["Access-Control-Allow-Origin"] = "*"
171             self.response.out.write(json)
172         else:
173             self.error(404)
174
175     def get(self):
176         key = self.request.get(PARAM_KEY)
177         master = self.request.get(PARAM_MASTER)
178         builder = self.request.get(PARAM_BUILDER)
179         test_type = self.request.get(PARAM_TEST_TYPE)
180         build_number = self.request.get(PARAM_BUILD_NUMBER, default_value=None)
181         name = self.request.get(PARAM_NAME)
182         before = self.request.get(PARAM_BEFORE)
183         num_files = self.request.get(PARAM_NUM_FILES)
184         test_list_json = self.request.get(PARAM_TEST_LIST_JSON)
185         callback_name = self.request.get(PARAM_CALLBACK)
186
187         logging.debug(
188             "Getting files, master %s, builder: %s, test_type: %s, build_number: %s, name: %s, before: %s.",
189             master, builder, test_type, build_number, name, before)
190
191         if key:
192             json, date = self._get_file_content_from_key(key)
193         elif num_files or not master or not builder or not test_type or (not build_number and not JsonResults.is_aggregate_file(name)) or not name:
194             limit = int(num_files) if num_files else 100
195             self._get_file_list(master, builder, test_type, build_number, name, before, limit, callback_name)
196             return
197         else:
198             # FIXME: Stop using the old master name style after all files have been updated.
199             master_data = master_config.getMaster(master)
200             if not master_data:
201                 master_data = master_config.getMasterByMasterName(master)
202             if not master_data:
203                 self.error(404)
204                 return
205
206             json, date = self._get_file_content(master_data['url_name'], builder, test_type, build_number, name)
207             if json is None:
208                 json, date = self._get_file_content(master_data['name'], builder, test_type, build_number, name)
209
210             if json and test_list_json:
211                 json = JsonResults.get_test_list(builder, json)
212
213         if json:
214             json = _replace_jsonp_callback(json, callback_name)
215
216         self._serve_json(json, date)
217
218
219 class Upload(webapp2.RequestHandler):
220     """Upload test results file to datastore."""
221
222     def post(self):
223         file_params = self.request.POST.getall(PARAM_FILE)
224         if not file_params:
225             self.response.out.write("FAIL: missing upload file field.")
226             return
227
228         builder = self.request.get(PARAM_BUILDER)
229         if not builder:
230             self.response.out.write("FAIL: missing builder parameter.")
231             return
232
233         master_parameter = self.request.get(PARAM_MASTER)
234
235         master_data = master_config.getMasterByMasterName(master_parameter)
236         if master_data:
237             deprecated_master = master_parameter
238             master = master_data['url_name']
239         else:
240             deprecated_master = None
241             master = master_parameter
242
243         test_type = self.request.get(PARAM_TEST_TYPE)
244
245         logging.debug(
246             "Processing upload request, master: %s, builder: %s, test_type: %s.",
247             master, builder, test_type)
248
249         # There are two possible types of each file_params in the request:
250         # one file item or a list of file items.
251         # Normalize file_params to a file item list.
252         files = []
253         logging.debug("test: %s, type:%s", file_params, type(file_params))
254         for item in file_params:
255             if not isinstance(item, list) and not isinstance(item, tuple):
256                 item = [item]
257             files.extend(item)
258
259         errors = []
260         final_status_code = 200
261         for file in files:
262             file_json = JsonResults._load_json(file.value)
263             if file.filename == "incremental_results.json":
264                 # FIXME: Ferret out and eliminate remaining incremental_results.json producers.
265                 logging.info("incremental_results.json received from master: %s, builder: %s, test_type: %s.",
266                              master, builder, test_type)
267                 status_string, status_code = JsonResults.update(master, builder, test_type, file_json,
268                     deprecated_master=deprecated_master, is_full_results_format=False)
269             else:
270                 try:
271                     build_number = int(file_json.get('build_number', 0))
272                     status_string, status_code = TestFile.add_file(master, builder, test_type, build_number, file.filename, file.value)
273                 except (ValueError, TypeError):
274                     status_code = 403
275                     status_string = 'Could not cast the build_number field in the json to an integer.'
276
277                 if status_code == 200:
278                     logging.info(status_string)
279                 else:
280                     logging.error(status_string)
281                     errors.append(status_string)
282                     final_status_code = status_code
283
284             if status_code == 200 and file.filename == "full_results.json":
285                 status_string, status_code = JsonResults.update(master, builder, test_type, file_json,
286                     deprecated_master=deprecated_master, is_full_results_format=True)
287
288             if status_code == 200:
289                 logging.info(status_string)
290             else:
291                 logging.error(status_string)
292                 errors.append(status_string)
293                 final_status_code = status_code
294
295         if errors:
296             messages = "FAIL: " + "; ".join(errors)
297             self.response.set_status(final_status_code, messages)
298             self.response.out.write(messages)
299         else:
300             self.response.set_status(200)
301             self.response.out.write("OK")
302
303
304 class UploadForm(webapp2.RequestHandler):
305     """Show a form so user can upload a file."""
306
307     def get(self):
308         template_values = {
309             "upload_url": "/testfile/upload",
310         }
311         self.response.out.write(template.render("templates/uploadform.html",
312                                                 template_values))