1/*
2 Available variables and functions:
3 <entry>.<entryMethod>(...) [raise <code>]
4 ... Calls the entry method synchronously. It does not error out if "raise <code>" is given and it has thrown the error code <code>.
5 reset() ... Removes all entries in the file system.
6 print(<arg>) ... Prints out the given arg.
7 ASSERT_EQ(<arg1>, <arg2>) ... Asserts that <arg1> and <arg2> are equal.
8 ASSERT_GE(<arg1>, <arg2>) ... Asserts that <arg1> is greater than or equals to <arg2>.
9 ROOT ... The file system's root entry.
10
11 Example:
12 -------------------------------------------------------------------------
13 <div id="file-system-test-script">
14 reset()
15
16 # comment
17 entry1 = ROOT.getFile('foo/a.txt')
18 entry2 = ROOT.getDirectory('foo/bar', {CREATE:true})
19 entry3 = ROOT.getFile('foo/nonexistent') raises 8
20 entries = ROOT.readDirectory()
21 print(entries)
22 entry1.copyTo(entry2, 'b.txt')
23 metadata = entry1.getMetadata()
24 print(metadata)
25 ASSERT_GT(metadata, 0)
26 </div>
27
28 var shell = new FileSystemTestShell(TEMPORARY, 100);
29 shell.runScripts("file-system-test-script");
30 -------------------------------------------------------------------------
31 */
32
33var FileSystemTestShell = function(type, size, opt_logFunc, opt_endTestCallback)
34{
35 this.fileSystem = null;
36 this.fileSystemType = type;
37 this.fileSystemSize = size;
38
39 this.log = opt_logFunc;
40 this.endTestCallback = opt_endTestCallback;
41
42 this.lines = [];
43 this.lineCounter = -1;
44 this.statement = '';
45 this.expectedErrorCode = 0;
46 this.destinationSymbol = null;
47 this.directoryReader = null;
48
49 this.vars = { 'true': true, 'false': false };
50 this.argSymbols = [];
51
52 this.runScripts = function(elementId)
53 {
54 if (!this.endTestCallback)
55 this.endTestCallback = function() { };
56
57 if (!this.log)
58 this.log = this.defaultLogFunc;
59
60 var elem = document.getElementById(elementId);
61 if (!elem) {
62 this.error('Element not found: ' + elementId);
63 return;
64 }
65 this.lines = elem.innerHTML.split(/\r?\n+/);
66 if (!this.lines.length) {
67 this.error("No scripts to run!");
68 return;
69 }
70
71 this.lineCounter = 0;
72 requestFileSystem(this.fileSystemType, this.fileSystemSize, this.bind(this.fileSystemCallback), this.bind(this.errorCallback));
73 }
74
75 // Callbacks ------------------------------------------------------------
76
77 this.bind = function(callback, arg1, arg2, arg3)
78 {
79 var obj = this;
80 return function(arg) {
81 if (arg == undefined)
82 callback.apply(obj, [arg1, arg2, arg3]);
83 else
84 callback.apply(obj, [arg, arg1, arg2, arg3]);
85 };
86 };
87
88 this.errorCallback = function(error)
89 {
90 if (window.shouldBe)
91 shouldBe.apply(this, ["this.expectedErrorCode", '"' + error.code + '"']);
92 if (this.expectedErrorCode && error.code == this.expectedErrorCode) {
93 this.runNextStatement();
94 } else {
95 this.error('Got error: ' + error.code);
96 }
97 }
98
99 this.successCallback = function(returnValue)
100 {
101 if (this.expectedErrorCode) {
102 this.error('Operation unexpectedly succeeded');
103 return;
104 }
105 if (this.destinationSymbol)
106 this.vars[this.destinationSymbol] = returnValue;
107 if (this.statement && window.testPassed)
108 testPassed('Succeeded: ' + this.statement);
109 this.runNextStatement();
110 }
111
112 this.entriesCallback = function(entries)
113 {
114 if (this.destinationSymbol) {
115 if (!this.vars[this.destinationSymbol])
116 this.vars[this.destinationSymbol] = [];
117 for (var i = 0; i < entries.length; ++i)
118 this.vars[this.destinationSymbol].push(entries[i]);
119 }
120 if (entries.length)
121 this.directoryReader.readEntries(this.bind(this.entriesCallback), this.bind(this.errorCallback));
122 else
123 this.runNextStatement();
124 }
125
126 this.fileSystemCallback = function(fs)
127 {
128 this.log('Got FileSystem: ' + fs.name);
129 this.fileSystem = fs;
130 this.vars['ROOT'] = fs.root;
131 this.runNextStatement();
132 }
133
134 // Helper methods for calling entry methods -----------------------------
135
136 var callEntryMethod0 = function(shell, entry, method, args)
137 {
138 try {
139 entry[method](shell.bind(shell.successCallback), shell.bind(shell.errorCallback));
140 } catch (exception) {
141 if (shell.expectedErrorCode && error.code == shell.expectedErrorCode) {
142 shell.log('Got exception (expected): ' + error.code);
143 shell.runNextStatement();
144 } else {
145 shell.error('Got exception: ' + error.code);
146 }
147 }
148 }
149
150 var callEntryMethod2 = function(shell, entry, method, args)
151 {
152 try {
153 entry[method](args[0], args[1], shell.bind(shell.successCallback), shell.bind(shell.errorCallback));
154 } catch (exception) {
155 if (shell.expectedErrorCode && error.code == shell.expectedErrorCode) {
156 shell.log('Got exception (expected): ' + error.code);
157 shell.runNextStatement();
158 } else {
159 shell.error('Got exception: ' + error.code);
160 }
161 }
162 }
163
164 var callReadDirectory = function(shell, entry, method, args)
165 {
166 try {
167 shell.directoryReader = entry.createReader();
168 shell.directoryReader.readEntries(shell.bind(shell.entriesCallback), shell.bind(shell.errorCallback));
169 } catch (exception) {
170 if (shell.expectedErrorCode && error.code == shell.expectedErrorCode) {
171 shell.log('Got exception (expected): ' + error.code);
172 shell.runNextStatement();
173 } else {
174 shell.error('Got exception: ' + error.code);
175 }
176 }
177 }
178
179 var entryMethodsTable = {
180 'getMetadata': callEntryMethod0,
181 'copyTo': callEntryMethod2,
182 'moveTo': callEntryMethod2,
183 'remove': callEntryMethod0,
184 'getParent': callEntryMethod0,
185 'getFile': callEntryMethod2,
186 'getDirectory': callEntryMethod2,
187 'readDirectory': callReadDirectory,
188 };
189
190 // Built-in funcitons ---------------------------------------------------
191
192 var toString = function(obj)
193 {
194 if (obj == undefined) {
195 return "undefined";
196 } else if (typeof obj == 'object') {
197 if (obj.length != undefined) {
198 var stringArray = [];
199 for (var i = 0; i < obj.length; ++i)
200 stringArray.push(toString(obj[i]));
201 stringArray.sort();
202 return '[' + stringArray.join(',\n ') + ']';
203 } else if (obj.isFile || obj.isDirectory) {
204 return 'ENTRY {path:' + obj.fullPath + ' name:' + obj.name + (obj.isFile ? ' type:FILE' : ' type:DIRECTORY') + '}';
205 } else {
206 return obj;
207 }
208 } else
209 return obj;
210 }
211
212 this.BUILTIN_print = function(args)
213 {
214 this.log(toString(args[0]));
215 this.runNextStatement();
216 }
217
218 this.BUILTIN_reset = function(args)
219 {
220 this.log('Reseting the filesystem...');
221 var shell = this;
222 removeRecursively(this.fileSystem.root, function() {
223 shell.runNextStatement();
224 }, this.bind(this.errorCallback));
225 }
226
227 this.BUILTIN_ASSERT = function(args)
228 {
229 var obj = args[0];
230 if (window.shouldBeNonNull)
231 shouldBeNonNull('"' + obj + '"');
232 if (!obj)
233 this.error('Assertion error:' + obj);
234 else
235 this.runNextStatement();
236 }
237
238 this.BUILTIN_ASSERT_GE = function(args)
239 {
240 var value1 = (args[0] instanceof Date) ? args[0].getTime() : args[0];
241 var value2 = (args[1] instanceof Date) ? args[1].getTime() : args[1];
242 if (window.shouldBeGreaterThanOrEqual)
243 shouldBeGreaterThanOrEqual('"' + value1 + '"', '"' + value2 + '"');
244 if (value1 < value2) {
245 this.error('Assertion error:' + args[0] + ' >= ' + args[1]);
246 return;
247 }
248 this.runNextStatement();
249 }
250
251 this.BUILTIN_ASSERT_EQ = function(args)
252 {
253 var value1 = (args[0] instanceof Date) ? args[0].getTime() : args[0];
254 var value2 = (args[1] instanceof Date) ? args[1].getTime() : args[1];
255 if (window.shouldBe)
256 shouldBe.apply(this, [this.argSymbols[1], this.argSymbols[0]]);
257 if (value1 != value2) {
258 this.error('Assertion error:' + args[0] + ' == ' + args[1]);
259 return;
260 }
261 this.runNextStatement();
262 }
263
264 // Parser ---------------------------------------------------------------
265
266 this.ENTRY_METHOD_RE = /^\s*([A-Za-z_]\w*)\.(getMetadata|moveTo|copyTo|remove|getParent|getFile|getDirectory|readDirectory)\((.*)\)(?:\s+raises?\s*\(?(\d+)\)?|)/;
267 this.ASSIGNMENT_RE = /^\s*([A-Za-z_]\w*)\s*=\s*(.*)/;
268
269 this.parseArgs = function(text) {
270 this.argSymbols = [];
271 var args = [];
272 var shell = this;
273 var vars = this.vars;
274 text.replace(/(?:({[^}]*})|(\d+|"[^"]*"|'[^']*')|([a-zA-Z_]\w*)(?:\[(\d+|'\w+'|"\w+")\]|\.(\w+)|))(,|$)\s*/g, function(match, hash, immediate, symbol, field1, field2) {
275 var field = field1 != undefined ? field1 : field2;
276 var obj = shell.vars[symbol];
277 if (symbol && obj == undefined) {
278 if (symbol == 'null' || symbol == 'undefined') {
279 obj = null;
280 } else {
281 shell.error('Undefined symbol:' + symbol);
282 return;
283 }
284 }
285 shell.argSymbols.push(
286 hash ? hash :
287 immediate ? immediate :
288 symbol ? 'this.vars["' + symbol + '"]' + (field != undefined ? '["' + field + '"]' : '') : match);
289 args.push(hash ? eval('o=' + hash)
290 : immediate ? eval(immediate)
291 : symbol ? (field != undefined ? obj[field] : obj)
292 : null);
293 });
294 return args;
295 }
296
297 this.runNextStatement = function()
298 {
299 if (this.lineCounter < 0 || this.lineCounter >= this.lines.length) {
300 this.log('Script execution finished.');
301 this.endTestCallback();
302 return;
303 }
304 this.statement = this.lines[this.lineCounter++];
305 if (this.statement.search(/^\s*(#.*|)$/) == 0) {
306 this.runNextStatement();
307 return;
308 }
309 var exp = this.statement;
310 this.destinationSymbol = null;
311 this.expectedErrorCode = 0;
312 var matches = this.statement.match(this.ASSIGNMENT_RE);
313 if (matches) {
314 this.destinationSymbol = matches[1];
315 this.vars[this.destinationSymbol] = null;
316 exp = matches[2];
317 }
318 matches = exp.match(this.ENTRY_METHOD_RE);
319 if (matches) {
320 var entry = shell.vars[matches[1]];
321 var method = matches[2];
322 if (!entry) {
323 this.error('No such symbol:' + matches[1]);
324 return;
325 }
326 this.expectedErrorCode = matches[4];
327 entryMethodsTable[method](this, entry, method, this.parseArgs(matches[3]));
328 return;
329 }
330 matches = exp.match(/^\s*([A-Za-z_]\w+)\((.*)\)/);
331 if (matches && this['BUILTIN_' + matches[1]]) {
332 this['BUILTIN_' + matches[1]](this.parseArgs(matches[2]));
333 return;
334 }
335 var values = this.parseArgs(exp);
336 if (values.length == 1) {
337 if (this.destinationSymbol)
338 this.vars[this.destinationSymbol] = values[0];
339 this.runNextStatement();
340 return;
341 }
342 this.error('Syntax error:' + this.statement);
343 }
344
345 // Misc -----------------------------------------------------------------
346
347 this.dumpVars = function()
348 {
349 for (var key in this.vars)
350 this.log('** ' + key + ':' + this.vars[key]);
351 }
352
353 this.error = function(msg)
354 {
355 var printError = this.log
356 if (window.testFailed)
357 printError = testFailed;
358 if (this.lineCounter > 0 && this.lineCounter < this.lines.length) {
359 printError('ERROR:' + msg + ' at line ' + this.lineCounter);
360 printError(' > ' + this.statement);
361 } else
362 printError('ERROR:' + msg);
363 this.endTestCallback();
364 }
365
366 this.defaultLogFunc = function(msg)
367 {
368 var elem = document.getElementById('console');
369 if (!elem) {
370 elem = document.createElement('div');
371 elem.id = 'console';
372 document.body.appendChild(elem);
373 }
374 var span = document.createElement('span');
375 document.getElementById("console").appendChild(span);
376 span.innerHTML = msg + '<br />';
377 }
378};