Add JSC:RegExp functional tests
[WebKit-https.git] / Source / JavaScriptCore / testRegExp.cpp
1 /*
2  *  Copyright (C) 2011 Apple Inc. All rights reserved.
3  *
4  *  This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Library General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  *  This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Library General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Library General Public License
15  *  along with this library; see the file COPYING.LIB.  If not, write to
16  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  *  Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "RegExp.h"
23
24 #include "CurrentTime.h"
25 #include "InitializeThreading.h"
26 #include "JSGlobalObject.h"
27 #include "UStringBuilder.h"
28 #include <errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #if !OS(WINDOWS)
34 #include <unistd.h>
35 #endif
36
37 #if HAVE(SYS_TIME_H)
38 #include <sys/time.h>
39 #endif
40
41 #if COMPILER(MSVC) && !OS(WINCE)
42 #include <crtdbg.h>
43 #include <mmsystem.h>
44 #include <windows.h>
45 #endif
46
47 #if PLATFORM(QT)
48 #include <QCoreApplication>
49 #include <QDateTime>
50 #endif
51
52 const int MaxLineLength = 100 * 1024;
53
54 using namespace JSC;
55 using namespace WTF;
56
57 static void cleanupGlobalData(JSGlobalData*);
58
59 struct Options {
60     Options()
61         : interactive(false)
62         , verbose(false)
63     {
64     }
65
66     bool interactive;
67     bool verbose;
68     Vector<UString> arguments;
69     Vector<UString> files;
70 };
71
72 class StopWatch {
73 public:
74     void start();
75     void stop();
76     long getElapsedMS(); // call stop() first
77
78 private:
79     double m_startTime;
80     double m_stopTime;
81 };
82
83 void StopWatch::start()
84 {
85     m_startTime = currentTime();
86 }
87
88 void StopWatch::stop()
89 {
90     m_stopTime = currentTime();
91 }
92
93 long StopWatch::getElapsedMS()
94 {
95     return static_cast<long>((m_stopTime - m_startTime) * 1000);
96 }
97
98 struct RegExpTest {
99     RegExpTest()
100         : offset(0)
101         , result(0)
102     {
103     }
104
105     UString subject;
106     int offset;
107     int result;
108     Vector<int, 32> expectVector;
109 };
110
111 class GlobalObject : public JSGlobalObject {
112 private:
113     GlobalObject(JSGlobalData&, Structure*, const Vector<UString>& arguments);
114
115 public:
116     typedef JSGlobalObject Base;
117
118     static GlobalObject* create(JSGlobalData& globalData, Structure* structure, const Vector<UString>& arguments)
119     {
120         return new (allocateCell<GlobalObject>(globalData.heap)) GlobalObject(globalData, structure, arguments);
121     }
122     virtual UString className() const { return "global"; }
123 };
124
125 COMPILE_ASSERT(!IsInteger<GlobalObject>::value, WTF_IsInteger_GlobalObject_false);
126 ASSERT_CLASS_FITS_IN_CELL(GlobalObject);
127
128 GlobalObject::GlobalObject(JSGlobalData& globalData, Structure* structure, const Vector<UString>& arguments)
129     : JSGlobalObject(globalData, structure)
130 {
131     UNUSED_PARAM(arguments);
132 }
133
134 // Use SEH for Release builds only to get rid of the crash report dialog
135 // (luckily the same tests fail in Release and Debug builds so far). Need to
136 // be in a separate main function because the realMain function requires object
137 // unwinding.
138
139 #if COMPILER(MSVC) && !COMPILER(INTEL) && !defined(_DEBUG) && !OS(WINCE)
140 #define TRY       __try {
141 #define EXCEPT(x) } __except (EXCEPTION_EXECUTE_HANDLER) { x; }
142 #else
143 #define TRY
144 #define EXCEPT(x)
145 #endif
146
147 int realMain(int argc, char** argv, JSGlobalData*);
148
149 int main(int argc, char** argv)
150 {
151 #if OS(WINDOWS)
152 #if !OS(WINCE)
153     // Cygwin calls ::SetErrorMode(SEM_FAILCRITICALERRORS), which we will inherit. This is bad for
154     // testing/debugging, as it causes the post-mortem debugger not to be invoked. We reset the
155     // error mode here to work around Cygwin's behavior. See <http://webkit.org/b/55222>.
156     ::SetErrorMode(0);
157 #endif
158
159 #if defined(_DEBUG)
160     _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
161     _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
162     _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
163     _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
164     _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
165     _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
166 #endif
167
168     timeBeginPeriod(1);
169 #endif
170
171 #if PLATFORM(QT)
172     QCoreApplication app(argc, argv);
173 #endif
174
175     // Initialize JSC before getting JSGlobalData.
176     JSC::initializeThreading();
177
178     // We can't use destructors in the following code because it uses Windows
179     // Structured Exception Handling
180     int res = 0;
181     JSGlobalData* globalData = JSGlobalData::create(ThreadStackTypeLarge, LargeHeap).leakRef();
182     TRY
183         res = realMain(argc, argv, globalData);
184     EXCEPT(res = 3)
185
186     cleanupGlobalData(globalData);
187     return res;
188 }
189
190 static void cleanupGlobalData(JSGlobalData* globalData)
191 {
192     JSLock lock(SilenceAssertionsOnly);
193     globalData->clearBuiltinStructures();
194     globalData->heap.destroy();
195     globalData->deref();
196 }
197
198 static bool testOneRegExp(JSGlobalData& globalData, RegExp* regexp, RegExpTest* regExpTest, bool verbose, unsigned int lineNumber)
199 {
200     bool result = true;
201     Vector<int, 32> outVector;
202     outVector.resize(regExpTest->expectVector.size());
203     int matchResult = regexp->match(globalData, regExpTest->subject, regExpTest->offset, &outVector);
204
205     if (matchResult != regExpTest->result) {
206         result = false;
207         if (verbose)
208             printf("Line %d: results mismatch - expected %d got %d\n", lineNumber, regExpTest->result, matchResult);
209     } else if (matchResult != -1) {
210         if (outVector.size() != regExpTest->expectVector.size()) {
211             result = false;
212             if (verbose)
213                 printf("Line %d: output vector size mismatch - expected %lu got %lu\n", lineNumber, regExpTest->expectVector.size(), outVector.size());
214         } else if (outVector.size() % 2) {
215             result = false;
216             if (verbose)
217                 printf("Line %d: output vector size is odd (%lu), should be even\n", lineNumber, outVector.size());
218         } else {
219             // Check in pairs since the first value of the pair could be -1 in which case the second doesn't matter.
220             size_t pairCount = outVector.size() / 2;
221             for (size_t i = 0; i < pairCount; ++i) {
222                 size_t startIndex = i*2;
223                 if (outVector[startIndex] != regExpTest->expectVector[startIndex]) {
224                     result = false;
225                     if (verbose)
226                         printf("Line %d: output vector mismatch at index %lu - expected %d got %d\n", lineNumber, startIndex, regExpTest->expectVector[startIndex], outVector[startIndex]);
227                 }
228                 if ((i > 0) && (regExpTest->expectVector[startIndex] != -1) && (outVector[startIndex+1] != regExpTest->expectVector[startIndex+1])) {
229                     result = false;
230                     if (verbose)
231                         printf("Line %d: output vector mismatch at index %lu - expected %d got %d\n", lineNumber, startIndex+1, regExpTest->expectVector[startIndex+1], outVector[startIndex+1]);
232                 }
233             }
234         }
235     }
236
237     return result;
238 }
239
240 static int scanString(char* buffer, int bufferLength, UStringBuilder& builder, char termChar)
241 {
242     bool escape = false;
243     
244     for (int i = 0; i < bufferLength; ++i) {
245         UChar c = buffer[i];
246         
247         if (escape) {
248             switch (c) {
249             case '0':
250                 c = '\0';
251                 break;
252             case 'a':
253                 c = '\a';
254                 break;
255             case 'b':
256                 c = '\b';
257                 break;
258             case 'f':
259                 c = '\f';
260                 break;
261             case 'n':
262                 c = '\n';
263                 break;
264             case 'r':
265                 c = '\r';
266                 break;
267             case 't':
268                 c = '\t';
269                 break;
270             case 'v':
271                 c = '\v';
272                 break;
273             case '\\':
274                 c = '\\';
275                 break;
276             case '?':
277                 c = '\?';
278                 break;
279             case 'u':
280                 if ((i + 4) >= bufferLength)
281                     return -1;
282                 unsigned int charValue;
283                 if (sscanf(buffer+i+1, "%04x", &charValue) != 1)
284                     return -1;
285                 c = static_cast<UChar>(charValue);
286                 i += 4;
287                 break;
288             }
289             
290             builder.append(c);
291             escape = false;
292         } else {
293             if (c == termChar)
294                 return i;
295
296             if (c == '\\')
297                 escape = true;
298             else
299                 builder.append(c);
300         }
301     }
302
303     return -1;
304 }
305
306 static RegExp* parseRegExpLine(JSGlobalData& globalData, char* line, int lineLength)
307 {
308     UStringBuilder pattern;
309     
310     if (line[0] != '/')
311         return 0;
312
313     int i = scanString(line + 1, lineLength - 1, pattern, '/') + 1;
314
315     if ((i >= lineLength) || (line[i] != '/'))
316         return 0;
317
318     ++i;
319
320     return RegExp::create(globalData, pattern.toUString(), regExpFlags(line + i));
321 }
322
323 static RegExpTest* parseTestLine(char* line, int lineLength)
324 {
325     UStringBuilder subjectString;
326     
327     if ((line[0] != ' ') || (line[1] != '"'))
328         return 0;
329
330     int i = scanString(line + 2, lineLength - 2, subjectString, '"') + 2;
331
332     if ((i >= (lineLength - 2)) || (line[i] != '"') || (line[i+1] != ',') || (line[i+2] != ' '))
333         return 0;
334
335     i += 3;
336     
337     int offset;
338     
339     if (sscanf(line + i, "%d, ", &offset) != 1)
340         return 0;
341
342     while (line[i] && line[i] != ' ')
343         ++i;
344
345     ++i;
346     
347     int matchResult;
348     
349     if (sscanf(line + i, "%d, ", &matchResult) != 1)
350         return 0;
351     
352     while (line[i] && line[i] != ' ')
353         ++i;
354     
355     ++i;
356     
357     if (line[i++] != '(')
358         return 0;
359
360     int start, end;
361     
362     RegExpTest* result = new RegExpTest();
363     
364     result->subject = subjectString.toUString();
365     result->offset = offset;
366     result->result = matchResult;
367
368     while (line[i] && line[i] != ')') {
369         if (sscanf(line + i, "%d, %d", &start, &end) != 2) {
370             delete result;
371             return 0;
372         }
373
374         result->expectVector.append(start);
375         result->expectVector.append(end);
376
377         while (line[i] && (line[i] != ',') && (line[i] != ')'))
378             i++;
379         i++;
380         while (line[i] && (line[i] != ',') && (line[i] != ')'))
381             i++;
382
383         if (line[i] == ')')
384             break;
385         if (!line[i] || (line[i] != ',')) {
386             delete result;
387             return 0;
388         }
389         i++;
390     }
391
392     return result;
393 }
394
395 static bool runFromFiles(GlobalObject* globalObject, const Vector<UString>& files, bool verbose)
396 {
397     UString script;
398     UString fileName;
399     Vector<char> scriptBuffer;
400     unsigned tests = 0;
401     unsigned failures = 0;
402     char* lineBuffer = new char[MaxLineLength + 1];
403
404     JSGlobalData& globalData = globalObject->globalData();
405
406     bool success = true;
407     for (size_t i = 0; i < files.size(); i++) {
408         FILE* testCasesFile = fopen(files[i].utf8().data(), "rb");
409
410         if (!testCasesFile) {
411             printf("Unable to open test data file \"%s\"\n", files[i].utf8().data());
412             continue;
413         }
414             
415         RegExp* regexp = 0;
416         size_t lineLength = 0;
417         char* linePtr = 0;
418         unsigned int lineNumber = 0;
419
420         while ((linePtr = fgets(&lineBuffer[0], MaxLineLength, testCasesFile))) {
421             lineLength = strlen(linePtr);
422             if (linePtr[lineLength - 1] == '\n') {
423                 linePtr[lineLength - 1] = '\0';
424                 --lineLength;
425             }
426             ++lineNumber;
427
428             if (linePtr[0] == '#')
429                 continue;
430
431             if (linePtr[0] == '/') {
432                 regexp = parseRegExpLine(globalData, linePtr, lineLength);
433             } else if (linePtr[0] == ' ') {
434                 RegExpTest* regExpTest = parseTestLine(linePtr, lineLength);
435                 
436                 if (regexp && regExpTest) {
437                     ++tests;
438                     if (!testOneRegExp(globalData, regexp, regExpTest, verbose, lineNumber)) {
439                         failures++;
440                         printf("Failure on line %u\n", lineNumber);
441                     }
442                 }
443                 
444                 if (regExpTest)
445                     delete regExpTest;
446             }
447         }
448         
449         fclose(testCasesFile);
450     }
451
452     if (failures)
453         printf("%u tests run, %u failures\n", tests, failures);
454     else
455         printf("%u tests passed\n", tests);
456
457     delete[] lineBuffer;
458
459     globalData.dumpSampleData(globalObject->globalExec());
460 #if ENABLE(REGEXP_TRACING)
461     globalData.dumpRegExpTrace();
462 #endif
463     return success;
464 }
465
466 #define RUNNING_FROM_XCODE 0
467
468 static NO_RETURN void printUsageStatement(JSGlobalData* globalData, bool help = false)
469 {
470     fprintf(stderr, "Usage: regexp_test [options] file\n");
471     fprintf(stderr, "  -h|--help  Prints this help message\n");
472     fprintf(stderr, "  -v|--verbose  Verbose output\n");
473
474     cleanupGlobalData(globalData);
475     exit(help ? EXIT_SUCCESS : EXIT_FAILURE);
476 }
477
478 static void parseArguments(int argc, char** argv, Options& options, JSGlobalData* globalData)
479 {
480     int i = 1;
481     for (; i < argc; ++i) {
482         const char* arg = argv[i];
483         if (!strcmp(arg, "-h") || !strcmp(arg, "--help"))
484             printUsageStatement(globalData, true);
485         if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose"))
486             options.verbose = true;
487         else
488             options.files.append(argv[i]);
489     }
490
491     for (; i < argc; ++i)
492         options.arguments.append(argv[i]);
493 }
494
495 int realMain(int argc, char** argv, JSGlobalData* globalData)
496 {
497     JSLock lock(SilenceAssertionsOnly);
498
499     Options options;
500     parseArguments(argc, argv, options, globalData);
501
502     GlobalObject* globalObject = GlobalObject::create(*globalData, GlobalObject::createStructure(*globalData, jsNull()), options.arguments);
503     bool success = runFromFiles(globalObject, options.files, options.verbose);
504
505     return success ? 0 : 3;
506 }