Changeset 64687 in webkit
- Timestamp:
- Aug 4, 2010 4:50:58 PM (14 years ago)
- Location:
- trunk/WebKitTools
- Files:
-
- 3 added
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/WebKitTools/ChangeLog
r64673 r64687 1 2010-08-04 Victor Wang <victorw@chromium.org> 2 3 Reviewed by Ojan Vafai. 4 5 -. Add result.json incremental merging functionality to test results app engine. 6 -. blobstore is not suitable for AE data merging and there is no API to 7 programatically edit existing blob or write a new one yet, so replace blobstore 8 with datastore. If file is oversize (>1000*1000 bytes), store file data in 9 multiple datastore entries. 10 -. Fix styles. 11 12 Test: jsonresults_unittest to test merging logics. 13 14 https://bugs.webkit.org/show_bug.cgi?id=38599 15 16 * TestResultServer/handlers/dashboardhandler.py: 17 * TestResultServer/handlers/menu.py: 18 * TestResultServer/handlers/testfilehandler.py: 19 * TestResultServer/main.py: 20 * TestResultServer/model/dashboardfile.py: 21 * TestResultServer/model/datastorefile.py: Added. 22 * TestResultServer/model/jsonresults.py: Added. 23 * TestResultServer/model/jsonresults_unittest.py: Added. 24 * TestResultServer/model/testfile.py: 25 * TestResultServer/templates/uploadform.html: 26 1 27 2010-08-04 Antonio Gomes <tonikitoo@webkit.org> 2 28 -
trunk/WebKitTools/TestResultServer/handlers/dashboardhandler.py
r56635 r64687 4 4 # modification, are permitted provided that the following conditions are 5 5 # met: 6 # 6 # 7 7 # * Redistributions of source code must retain the above copyright 8 8 # notice, this list of conditions and the following disclaimer. … … 14 14 # contributors may be used to endorse or promote products derived from 15 15 # this software without specific prior written permission. 16 # 16 # 17 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT … … 38 38 39 39 PARAM_FILE = "file" 40 40 41 41 42 def get_content_type(filename): -
trunk/WebKitTools/TestResultServer/handlers/menu.py
r56753 r64687 4 4 # modification, are permitted provided that the following conditions are 5 5 # met: 6 # 6 # 7 7 # * Redistributions of source code must retain the above copyright 8 8 # notice, this list of conditions and the following disclaimer. … … 14 14 # contributors may be used to endorse or promote products derived from 15 15 # this software without specific prior written permission. 16 # 16 # 17 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT … … 62 62 self.response.out.write( 63 63 template.render("templates/menu.html", template_values)) 64 -
trunk/WebKitTools/TestResultServer/handlers/testfilehandler.py
r56635 r64687 4 4 # modification, are permitted provided that the following conditions are 5 5 # met: 6 # 6 # 7 7 # * Redistributions of source code must retain the above copyright 8 8 # notice, this list of conditions and the following disclaimer. … … 14 14 # contributors may be used to endorse or promote products derived from 15 15 # this software without specific prior written permission. 16 # 16 # 17 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT … … 31 31 32 32 from google.appengine.api import users 33 from google.appengine.ext import blobstore34 33 from google.appengine.ext import webapp 35 from google.appengine.ext.webapp import blobstore_handlers36 34 from google.appengine.ext.webapp import template 37 35 36 from model.jsonresults import JsonResults 38 37 from model.testfile import TestFile 39 38 … … 44 43 PARAM_KEY = "key" 45 44 PARAM_TEST_TYPE = "testtype" 45 PARAM_INCREMENTAL = "incremental" 46 46 47 47 48 48 class DeleteFile(webapp.RequestHandler): 49 """Delete test file for a given builder and name from datastore (metadata) and blobstore (file data)."""49 """Delete test file for a given builder and name from datastore.""" 50 50 51 51 def get(self): … … 56 56 57 57 logging.debug( 58 "Deleting File, builder: %s, test_type: %s, name: %s, blobkey: %s.",58 "Deleting File, builder: %s, test_type: %s, name: %s, key: %s.", 59 59 builder, test_type, name, key) 60 60 … … 66 66 67 67 68 class GetFile( blobstore_handlers.BlobstoreDownloadHandler):68 class GetFile(webapp.RequestHandler): 69 69 """Get file content or list of files for given builder and name.""" 70 70 … … 78 78 """ 79 79 80 files = TestFile.get_files(builder, test_type, name, 100) 80 files = TestFile.get_files( 81 builder, test_type, name, load_data=False, limit=100) 81 82 if not files: 82 83 logging.info("File not found, builder: %s, test_type: %s, name: %s.", … … 104 105 """ 105 106 106 files = TestFile.get_files(builder, test_type, name, 1) 107 files = TestFile.get_files( 108 builder, test_type, name, load_data=True, limit=1) 107 109 if not files: 108 110 logging.info("File not found, builder: %s, test_type: %s, name: %s.", … … 110 112 return 111 113 112 blob_key = files[0].blob_key 113 blob_info = blobstore.get(blob_key) 114 if blob_info: 115 self.send_blob(blob_info, "text/plain") 114 self.response.headers["Content-Type"] = "text/plain; charset=utf-8" 115 self.response.out.write(files[0].data) 116 116 117 117 def get(self): … … 134 134 135 135 136 class GetUploadUrl(webapp.RequestHandler): 137 """Get an url for uploading file to blobstore. A special url is required for each blobsotre upload.""" 138 139 def get(self): 140 upload_url = blobstore.create_upload_url("/testfile/upload") 141 logging.info("Getting upload url: %s.", upload_url) 142 self.response.out.write(upload_url) 143 144 145 class Upload(blobstore_handlers.BlobstoreUploadHandler): 146 """Upload file to blobstore.""" 136 class Upload(webapp.RequestHandler): 137 """Upload test results file to datastore.""" 147 138 148 139 def post(self): 149 uploaded_files = self.get_uploads("file") 150 if not uploaded_files: 151 return self._upload_done([("Missing upload file field.")]) 140 file_params = self.request.POST.getall(PARAM_FILE) 141 if not file_params: 142 self.response.out.write("FAIL: missing upload file field.") 143 return 152 144 153 145 builder = self.request.get(PARAM_BUILDER) 154 146 if not builder: 155 for blob_info in uploaded_files: 156 blob_info.delete() 157 158 return self._upload_done([("Missing builder parameter in upload request.")]) 147 self.response.out.write("FAIL: missing builder parameter.") 148 return 159 149 160 150 test_type = self.request.get(PARAM_TEST_TYPE) 151 incremental = self.request.get(PARAM_INCREMENTAL) 161 152 162 153 logging.debug( … … 164 155 builder, test_type) 165 156 157 # There are two possible types of each file_params in the request: 158 # one file item or a list of file items. 159 # Normalize file_params to a file item list. 160 files = [] 161 logging.debug("test: %s, type:%s", file_params, type(file_params)) 162 for item in file_params: 163 if not isinstance(item, list) and not isinstance(item, tuple): 164 item = [item] 165 files.extend(item) 166 166 167 errors = [] 167 for blob_info in uploaded_files: 168 tf = TestFile.update_file(builder, test_type, blob_info) 169 if not tf: 168 for file in files: 169 filename = file.filename.lower() 170 if ((incremental and filename == "results.json") or 171 (filename == "incremental_results.json")): 172 # Merge incremental json results. 173 saved_file = JsonResults.update(builder, test_type, file.value) 174 else: 175 saved_file = TestFile.update( 176 builder, test_type, file.filename, file.value) 177 178 if not saved_file: 170 179 errors.append( 171 180 "Upload failed, builder: %s, test_type: %s, name: %s." % 172 (builder, test_type, blob_info.filename)) 173 blob_info.delete() 174 175 return self._upload_done(errors) 176 177 def _upload_done(self, errors): 178 logging.info("upload done.") 179 180 error_messages = [] 181 for error in errors: 182 logging.info(error) 183 error_messages.append("error=%s" % urllib.quote(error)) 184 185 if error_messages: 186 redirect_url = "/uploadfail?%s" % "&".join(error_messages) 181 (builder, test_type, file.filename)) 182 183 if errors: 184 messages = "FAIL: " + "; ".join(errors) 185 logging.warning(messages) 186 self.response.set_status(500, messages) 187 self.response.out.write("FAIL") 187 188 else: 188 redirect_url = "/uploadsuccess" 189 190 logging.info(redirect_url) 191 # BlobstoreUploadHandler requires redirect at the end. 192 self.redirect(redirect_url) 189 self.response.set_status(200) 190 self.response.out.write("OK") 193 191 194 192 195 193 class UploadForm(webapp.RequestHandler): 196 """Show a form so user can submit a file to blobstore."""194 """Show a form so user can upload a file.""" 197 195 198 196 def get(self): 199 upload_url = blobstore.create_upload_url("/testfile/upload")200 197 template_values = { 201 "upload_url": upload_url,198 "upload_url": "/testfile/upload", 202 199 } 203 200 self.response.out.write(template.render("templates/uploadform.html", 204 201 template_values)) 205 206 class UploadStatus(webapp.RequestHandler):207 """Return status of file uploading"""208 209 def get(self):210 logging.debug("Update status")211 212 if self.request.path == "/uploadsuccess":213 self.response.set_status(200)214 self.response.out.write("OK")215 else:216 errors = self.request.params.getall("error")217 if errors:218 messages = "FAIL: " + "; ".join(errors)219 logging.warning(messages)220 self.response.set_status(500, messages)221 self.response.out.write("FAIL") -
trunk/WebKitTools/TestResultServer/main.py
r56635 r64687 4 4 # modification, are permitted provided that the following conditions are 5 5 # met: 6 # 6 # 7 7 # * Redistributions of source code must retain the above copyright 8 8 # notice, this list of conditions and the following disclaimer. … … 14 14 # contributors may be used to endorse or promote products derived from 15 15 # this software without specific prior written permission. 16 # 16 # 17 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT … … 43 43 ('/dashboards/([^?]+)?', dashboardhandler.GetDashboardFile), 44 44 ('/testfile/delete', testfilehandler.DeleteFile), 45 ('/testfile/uploadurl', testfilehandler.GetUploadUrl),46 45 ('/testfile/upload', testfilehandler.Upload), 47 46 ('/testfile/uploadform', testfilehandler.UploadForm), 48 47 ('/testfile/?', testfilehandler.GetFile), 49 ('/uploadfail', testfilehandler.UploadStatus),50 ('/uploadsuccess', testfilehandler.UploadStatus),51 48 ('/*|/menu', menu.Menu), 52 49 ] 53 50 54 51 application = webapp.WSGIApplication(routes, debug=True) 52 55 53 56 54 def main(): -
trunk/WebKitTools/TestResultServer/model/dashboardfile.py
r56635 r64687 4 4 # modification, are permitted provided that the following conditions are 5 5 # met: 6 # 6 # 7 7 # * Redistributions of source code must retain the above copyright 8 8 # notice, this list of conditions and the following disclaimer. … … 14 14 # contributors may be used to endorse or promote products derived from 15 15 # this software without specific prior written permission. 16 # 16 # 17 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT … … 36 36 SVN_PATH_DASHBOARD = ("http://src.chromium.org/viewvc/chrome/trunk/tools/" 37 37 "dashboards/") 38 38 39 39 40 class DashboardFile(db.Model): … … 93 94 logging.info("No existing file, added as new file.") 94 95 return cls.add_file(name, data) 95 96 96 97 logging.debug("Updating existing file.") 97 98 file = files[0] -
trunk/WebKitTools/TestResultServer/model/testfile.py
r56635 r64687 4 4 # modification, are permitted provided that the following conditions are 5 5 # met: 6 # 6 # 7 7 # * Redistributions of source code must retain the above copyright 8 8 # notice, this list of conditions and the following disclaimer. … … 14 14 # contributors may be used to endorse or promote products derived from 15 15 # this software without specific prior written permission. 16 # 16 # 17 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT … … 30 30 import logging 31 31 32 from google.appengine.ext import blobstore33 32 from google.appengine.ext import db 34 33 34 from model.datastorefile import DataStoreFile 35 35 36 class TestFile(db.Model): 36 37 class TestFile(DataStoreFile): 37 38 builder = db.StringProperty() 38 name = db.StringProperty()39 39 test_type = db.StringProperty() 40 blob_key = db.StringProperty()41 date = db.DateTimeProperty(auto_now_add=True)42 40 43 41 @classmethod … … 64 62 65 63 @classmethod 66 def get_files(cls, builder, test_type, name, l imit):64 def get_files(cls, builder, test_type, name, load_data=True, limit=1): 67 65 query = TestFile.all() 68 66 if builder: … … 73 71 query = query.filter("name =", name) 74 72 75 return query.order("-date").fetch(limit) 73 files = query.order("-date").fetch(limit) 74 if load_data: 75 for file in files: 76 file.load_data() 77 78 return files 76 79 77 80 @classmethod 78 def add_file(cls, builder, test_type, blob_info):81 def add_file(cls, builder, test_type, name, data): 79 82 file = TestFile() 80 83 file.builder = builder 81 84 file.test_type = test_type 82 file.name = blob_info.filename 83 file.blob_key = str(blob_info.key()) 84 file.put() 85 file.name = name 86 87 if not file.save(data): 88 return None 85 89 86 90 logging.info( 87 "File saved, builder: %s, test_type: %s, name: %s, blobkey: %s.",88 builder, test_type, file.name, file.blob_key)91 "File saved, builder: %s, test_type: %s, name: %s, key: %s.", 92 builder, test_type, file.name, str(file.data_keys)) 89 93 90 94 return file 91 95 92 96 @classmethod 93 def update _file(cls, builder, test_type, blob_info):94 files = cls.get_files(builder, test_type, blob_info.filename, 1)97 def update(cls, builder, test_type, name, data): 98 files = cls.get_files(builder, test_type, name) 95 99 if not files: 96 return cls.add_file(builder, test_type, blob_info)100 return cls.add_file(builder, test_type, name, data) 97 101 98 102 file = files[0] 99 old_blob_info = blobstore.BlobInfo.get(file.blob_key) 100 if old_blob_info: 101 old_blob_info.delete() 102 103 file.builder = builder 104 file.test_type = test_type 105 file.name = blob_info.filename 106 file.blob_key = str(blob_info.key()) 107 file.date = datetime.now() 108 file.put() 103 if not file.save(data): 104 return None 109 105 110 106 logging.info( 111 "File replaced, builder: %s, test_type: %s, name: %s, blobkey: %s.",112 builder, test_type, file.name, file.blob_key)107 "File replaced, builder: %s, test_type: %s, name: %s, data key: %s.", 108 builder, test_type, file.name, str(file.data_keys)) 113 109 114 110 return file 115 111 112 def save(self, data): 113 if not self.save_data(data): 114 return False 115 116 self.date = datetime.now() 117 self.put() 118 119 return True 120 116 121 def _delete_all(self): 117 if self.blob_key: 118 blob_info = blobstore.BlobInfo.get(self.blob_key) 119 if blob_info: 120 blob_info.delete() 121 122 self.delete_data() 122 123 self.delete() -
trunk/WebKitTools/TestResultServer/templates/uploadform.html
r56635 r64687 19 19 </tr> 20 20 </table> 21 <br> 22 <div><input class=button type="checkbox" name="incremental">Incremental results, merge with server file.</div> 23 <br> 21 24 <div><input class=button type="file" name="file" multiple></div> 22 25 <br>
Note: See TracChangeset
for help on using the changeset viewer.