OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// Script that updates dart2js status lines automatically for tests under the |
| 6 /// '$dart2js_with_kernel' configuration. |
| 7 /// |
| 8 /// This script is hardcoded to only support this configuration and relies on |
| 9 /// a convention for how the status files are structured, In particular, |
| 10 /// every status file for dart2js should have 2 sections: |
| 11 /// |
| 12 /// [ $compiler == dart2js && $dart2js_with_kernel && $host_checked ] |
| 13 /// |
| 14 /// and: |
| 15 /// |
| 16 /// [ $compiler == dart2js && $dart2js_with_kernel && $minified ] |
| 17 library status_files.update_from_log; |
| 18 |
| 19 import 'dart:io'; |
| 20 |
| 21 import 'record.dart'; |
| 22 import 'log_parser.dart'; |
| 23 |
| 24 final configurations = { |
| 25 'checked': |
| 26 r'[ $compiler == dart2js && $dart2js_with_kernel && $host_checked ]', |
| 27 'minified': r'[ $compiler == dart2js && $dart2js_with_kernel && $minified ]', |
| 28 }; |
| 29 |
| 30 final statusFiles = { |
| 31 'language': 'tests/language/language_dart2js.status', |
| 32 'corelib': 'tests/corelib/corelib.status', |
| 33 'language_2': 'tests/language_2/language_2_dart2js.status', |
| 34 // TODO(sigmund,rnystrom): update when corelib_2 gets split into multiple |
| 35 // status files. |
| 36 'corelib_2': 'tests/corelib_2/corelib_2.status', |
| 37 'dart2js_extra': 'tests/compiler/dart2js_extra/dart2js_extra.status', |
| 38 'dart2js_native': 'tests/compiler/dart2js_native/dart2js_native.status', |
| 39 }; |
| 40 |
| 41 main(args) { |
| 42 if (args.length < 2) { |
| 43 print('usage: udpate_from_log.dart <mode> log.txt'); |
| 44 print(' where mode is one of these values: ${configurations.keys}'); |
| 45 exit(1); |
| 46 } |
| 47 var mode = args[0]; |
| 48 if (!configurations.containsKey(mode)) { |
| 49 print('invalid mode: $mode, expected one in ${configurations.keys}'); |
| 50 exit(1); |
| 51 } |
| 52 |
| 53 var uri = Uri.base.resolve(args[1]); |
| 54 var file = new File.fromUri(uri); |
| 55 if (!file.existsSync()) { |
| 56 print('file not found: $file'); |
| 57 exit(1); |
| 58 } |
| 59 |
| 60 updateLogs(mode, file.readAsStringSync()); |
| 61 } |
| 62 |
| 63 /// Update all status files based on the [log] records when running the compiler |
| 64 /// in [mode]. |
| 65 void updateLogs(String mode, String log) { |
| 66 List<Record> records = parse(log); |
| 67 records.sort(); |
| 68 var last; |
| 69 var section; |
| 70 for (var record in records) { |
| 71 if (last == record) continue; // ignore duplicates |
| 72 if (section?.suite != record.suite) { |
| 73 section?.update(); |
| 74 section = ConfigurationInSuiteSection.create(record.suite, mode); |
| 75 } |
| 76 section.add(record); |
| 77 last = record; |
| 78 } |
| 79 section?.update(); |
| 80 } |
| 81 |
| 82 /// Represents an existing entry in the logs. |
| 83 class ExistingEntry { |
| 84 final String test; |
| 85 final String status; |
| 86 |
| 87 ExistingEntry(this.test, this.status); |
| 88 |
| 89 static parse(String line) { |
| 90 var colonIndex = line.indexOf(':'); |
| 91 var test = line.substring(0, colonIndex); |
| 92 var status = line.substring(colonIndex + 1).trim(); |
| 93 var commentIndex = status.indexOf("#"); |
| 94 if (commentIndex != -1) { |
| 95 status = status.substring(0, commentIndex); |
| 96 } |
| 97 return new ExistingEntry(test, status); |
| 98 } |
| 99 } |
| 100 |
| 101 /// Represents a section in a .status file that corresponds to a specific suite |
| 102 /// and configuration. |
| 103 class ConfigurationInSuiteSection { |
| 104 final String suite; |
| 105 final String _statusFile; |
| 106 final String _contents; |
| 107 final int _begin; |
| 108 final int _end; |
| 109 final List<Record> _records = []; |
| 110 |
| 111 ConfigurationInSuiteSection( |
| 112 this.suite, this._statusFile, this._contents, this._begin, this._end); |
| 113 |
| 114 /// Add a new test record, indicating that the test status should be updated. |
| 115 void add(Record record) => _records.add(record); |
| 116 |
| 117 /// Update the section in the file. |
| 118 /// |
| 119 /// This will reflect the new status lines as recorded in [_records]. |
| 120 void update() { |
| 121 int changes = 0; |
| 122 int ignored = 0; |
| 123 var originalEntries = _contents.substring(_begin, _end).split('\n'); |
| 124 |
| 125 // The algorithm below walks entries in the file and from the log in the |
| 126 // same order: preserving entries that didn't change, and updating entries |
| 127 // where the logs show that the test status changed. |
| 128 |
| 129 // Records are already sorted, but we sort the file contents in case the |
| 130 // file has been tampered with. |
| 131 originalEntries.sort(); |
| 132 |
| 133 var newContents = new StringBuffer(); |
| 134 newContents.write(_contents.substring(0, _begin)); |
| 135 addFromRecord(Record record) { |
| 136 newContents.writeln('${record.test}: ${record.actual}'); |
| 137 } |
| 138 |
| 139 int i = 0, j = 0; |
| 140 while (i < originalEntries.length && j < _records.length) { |
| 141 var existingLine = originalEntries[i]; |
| 142 if (existingLine.trim().isEmpty) { |
| 143 i++; |
| 144 continue; |
| 145 } |
| 146 var existing = ExistingEntry.parse(existingLine); |
| 147 var record = _records[j]; |
| 148 var compare = existing.test.compareTo(record.test); |
| 149 if (compare < 0) { |
| 150 // Existing test was unaffected, copy the status line. |
| 151 newContents.writeln(existingLine); |
| 152 i++; |
| 153 } else if (compare > 0) { |
| 154 // New entry, if it's a failure, we haven't seen this before and must |
| 155 // add it. If the status says it is passing, we ignore it. We do this |
| 156 // to support making this script idempotent if the patching has already |
| 157 // been done. |
| 158 if (!record.isPassing) { |
| 159 // New failure never seen before |
| 160 addFromRecord(record); |
| 161 changes++; |
| 162 } |
| 163 j++; |
| 164 } else if (existing.status == record.actual) { |
| 165 // This also should only happen if the patching has already been done. |
| 166 // We don't complain to make this script idempotent. |
| 167 newContents.writeln(existingLine); |
| 168 ignored++; |
| 169 i++; |
| 170 j++; |
| 171 } else { |
| 172 changes++; |
| 173 // The status changed, if it is now passing, we omit the entry entirely, |
| 174 // otherwise we use the status from the logs. |
| 175 if (!record.isPassing) { |
| 176 addFromRecord(record); |
| 177 } |
| 178 i++; |
| 179 j++; |
| 180 } |
| 181 } |
| 182 |
| 183 for (; i < originalEntries.length; i++) { |
| 184 newContents.writeln(originalEntries[i]); |
| 185 } |
| 186 |
| 187 for (; j < _records.length; j++) { |
| 188 changes++; |
| 189 addFromRecord(_records[j]); |
| 190 } |
| 191 |
| 192 newContents.write('\n'); |
| 193 newContents.write(_contents.substring(_end)); |
| 194 new File(_statusFile).writeAsStringSync('$newContents'); |
| 195 print("updated '$_statusFile' with $changes changes"); |
| 196 if (ignored > 0) { |
| 197 print(' $ignored changes were already applied in the status file.'); |
| 198 } |
| 199 } |
| 200 |
| 201 static ConfigurationInSuiteSection create(String suite, String mode) { |
| 202 var statusFile = statusFiles[suite]; |
| 203 var contents = new File(statusFile).readAsStringSync(); |
| 204 var condition = configurations[mode]; |
| 205 int sectionDeclaration = contents.indexOf(condition); |
| 206 if (sectionDeclaration == -1) { |
| 207 print('error: unable to find condition $condition in $statusFile'); |
| 208 exit(1); |
| 209 } |
| 210 int begin = contents.indexOf('\n', sectionDeclaration) + 1; |
| 211 assert(begin != 0); |
| 212 int end = contents.indexOf('\n[', begin + 1); |
| 213 end = end == -1 ? contents.length : end + 1; |
| 214 return new ConfigurationInSuiteSection( |
| 215 suite, statusFile, contents, begin, end); |
| 216 } |
| 217 } |
OLD | NEW |