Reviewed by Geoff.
[WebKit-https.git] / JavaScriptCore / kjs / testkjs.cpp
1 // -*- c-basic-offset: 2 -*-
2 /*
3  *  This file is part of the KDE libraries
4  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
5  *  Copyright (C) 2004-2006 Apple Computer, Inc.
6  *  Copyright (C) 2006 Bj√∂rn Graf (bjoern.graf@gmail.com)
7  *
8  *  This library is free software; you can redistribute it and/or
9  *  modify it under the terms of the GNU Library General Public
10  *  License as published by the Free Software Foundation; either
11  *  version 2 of the License, or (at your option) any later version.
12  *
13  *  This library is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  *  Library General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Library General Public License
19  *  along with this library; see the file COPYING.LIB.  If not, write to
20  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  *  Boston, MA 02110-1301, USA.
22  *
23  */
24
25 #include "config.h"
26 #include "collector.h"
27
28 #include "HashTraits.h"
29 #include "JSLock.h"
30 #include "object.h"
31
32 #include <math.h>
33 #include <stdio.h>
34 #include <string.h>
35 #if HAVE(SYS_TIME_H)
36 #include <sys/time.h>
37 #endif
38
39 #include "protect.h"
40
41 #if PLATFORM(WIN_OS)
42 #include <windows.h>
43 #endif
44
45 using namespace KJS;
46 using namespace KXMLCore;
47
48 static void testIsInteger();
49 static char* createStringWithContentsOfFile(const char* fileName);
50
51 class StopWatch
52 {
53 public:
54     void start();
55     void stop();
56     long getElapsedMS(); // call stop() first
57     
58 private:
59 #if PLATFORM(WIN_OS)
60     DWORD m_startTime;
61     DWORD m_stopTime;
62 #else
63     // Windows does not have timeval, disabling this class for now (bug 7399)
64     timeval m_startTime;
65     timeval m_stopTime;
66 #endif
67 };
68
69 void StopWatch::start()
70 {
71 #if PLATFORM(WIN_OS)
72     m_startTime = timeGetTime();
73 #else
74     gettimeofday(&m_startTime, 0);
75 #endif
76 }
77
78 void StopWatch::stop()
79 {
80 #if PLATFORM(WIN_OS)
81     m_stopTime = timeGetTime();
82 #else
83     gettimeofday(&m_stopTime, 0);
84 #endif
85 }
86
87 long StopWatch::getElapsedMS()
88 {
89 #if PLATFORM(WIN_OS)
90     return m_stopTime - m_startTime;
91 #else
92     timeval elapsedTime;
93     timersub(&m_stopTime, &m_startTime, &elapsedTime);
94     
95     return elapsedTime.tv_sec * 1000 + lroundf(elapsedTime.tv_usec / 1000.0);
96 #endif
97 }
98
99 class GlobalImp : public JSObject {
100 public:
101   virtual UString className() const { return "global"; }
102 };
103
104 class TestFunctionImp : public JSObject {
105 public:
106   TestFunctionImp(int i, int length);
107   virtual bool implementsCall() const { return true; }
108   virtual JSValue* callAsFunction(ExecState* exec, JSObject* thisObj, const List &args);
109
110   enum { Print, Debug, Quit, GC, Version, Run };
111
112 private:
113   int id;
114 };
115
116 TestFunctionImp::TestFunctionImp(int i, int length) : JSObject(), id(i)
117 {
118   putDirect(lengthPropertyName,length,DontDelete|ReadOnly|DontEnum);
119 }
120
121 JSValue* TestFunctionImp::callAsFunction(ExecState* exec, JSObject*, const List &args)
122 {
123   switch (id) {
124     case Print:
125       printf("--> %s\n", args[0]->toString(exec).UTF8String().c_str());
126       return jsUndefined();
127     case Debug:
128       fprintf(stderr, "--> %s\n", args[0]->toString(exec).UTF8String().c_str());
129       return jsUndefined();
130     case GC:
131     {
132       JSLock lock;
133       Interpreter::collect();
134       return jsUndefined();
135     }
136     case Version:
137       // We need this function for compatibility with the Mozilla JS tests but for now
138       // we don't actually do any version-specific handling
139       return jsUndefined();
140     case Run:
141     {
142       StopWatch stopWatch;
143       char* fileName = strdup(args[0]->toString(exec).UTF8String().c_str());
144       char* script = createStringWithContentsOfFile(fileName);
145       if (!script)
146         return throwError(exec, GeneralError, "Could not open file.");
147
148       stopWatch.start();
149       exec->dynamicInterpreter()->evaluate(fileName, 0, script);
150       stopWatch.stop();
151
152       free(script);
153       free(fileName);
154       
155       return jsNumber(stopWatch.getElapsedMS());
156     }
157     case Quit:
158       exit(0);
159     default:
160       abort();
161   }
162   return 0;
163 }
164
165 #if PLATFORM(WIN_OS)
166
167 // Use SEH for Release builds only to get rid of the crash report dialog
168 // (luckyly the same tests fail in Release and Debug builds so far). Need to
169 // be in a separate main function because the kjsmain function requires object
170 // unwinding.
171
172 #if defined(_DEBUG)
173 #define TRY
174 #define EXCEPT(x)
175 #else
176 #define TRY       __try {
177 #define EXCEPT(x) } __except (EXCEPTION_EXECUTE_HANDLER) { x; }
178 #endif
179
180 #else
181
182 #define TRY
183 #define EXCEPT(x)
184
185 #endif
186
187 int kjsmain(int argc, char** argv);
188
189 int main(int argc, char** argv)
190 {
191     int res = 0;
192     TRY
193         res = kjsmain(argc, argv);
194     EXCEPT(res = 3)
195     return res;
196 }
197
198
199 bool doIt(int argc, char** argv)
200 {
201   bool success = true;
202   GlobalImp* global = new GlobalImp();
203
204   // create interpreter
205   Interpreter interp(global);
206   // add debug() function
207   global->put(interp.globalExec(), "debug", new TestFunctionImp(TestFunctionImp::Debug, 1));
208   // add "print" for compatibility with the mozilla js shell
209   global->put(interp.globalExec(), "print", new TestFunctionImp(TestFunctionImp::Print, 1));
210   // add "quit" for compatibility with the mozilla js shell
211   global->put(interp.globalExec(), "quit", new TestFunctionImp(TestFunctionImp::Quit, 0));
212   // add "gc" for compatibility with the mozilla js shell
213   global->put(interp.globalExec(), "gc", new TestFunctionImp(TestFunctionImp::GC, 0));
214   // add "version" for compatibility with the mozilla js shell 
215   global->put(interp.globalExec(), "version", new TestFunctionImp(TestFunctionImp::Version, 1));
216   global->put(interp.globalExec(), "run", new TestFunctionImp(TestFunctionImp::Run, 1));
217   
218   Interpreter::setShouldPrintExceptions(true);
219   
220   for (int i = 1; i < argc; i++) {
221     const char* fileName = argv[i];
222     if (strcmp(fileName, "-f") == 0) // mozilla test driver script uses "-f" prefix for files
223       continue;
224     
225     char* script = createStringWithContentsOfFile(fileName);
226     if (!script) {
227       success = false;
228       break; // fail early so we can catch missing files
229     }
230     
231     Completion completion = interp.evaluate(fileName, 0, script);
232     success = success && completion.complType() != Throw;
233     free(script);
234   }
235
236   return success;
237 }
238
239
240 int kjsmain(int argc, char** argv)
241 {
242   if (argc < 2) {
243     fprintf(stderr, "Usage: testkjs file1 [file2...]\n");
244     return -1;
245   }
246
247   testIsInteger();
248
249   JSLock lock;
250
251   bool success = doIt(argc, argv);
252
253 #ifndef NDEBUG
254   Collector::collect();
255 #endif
256
257   if (success)
258     fprintf(stderr, "OK.\n");
259   
260 #ifdef KJS_DEBUG_MEM
261   Interpreter::finalCheck();
262 #endif
263   return success ? 0 : 3;
264 }
265
266 static void testIsInteger()
267 {
268   // Unit tests for KXMLCore::IsInteger. Don't have a better place for them now.
269   // FIXME: move these once we create a unit test directory for KXMLCore.
270
271   assert(IsInteger<bool>::value);
272   assert(IsInteger<char>::value);
273   assert(IsInteger<signed char>::value);
274   assert(IsInteger<unsigned char>::value);
275   assert(IsInteger<short>::value);
276   assert(IsInteger<unsigned short>::value);
277   assert(IsInteger<int>::value);
278   assert(IsInteger<unsigned int>::value);
279   assert(IsInteger<long>::value);
280   assert(IsInteger<unsigned long>::value);
281   assert(IsInteger<long long>::value);
282   assert(IsInteger<unsigned long long>::value);
283
284   assert(!IsInteger<char*>::value);
285   assert(!IsInteger<const char* >::value);
286   assert(!IsInteger<volatile char* >::value);
287   assert(!IsInteger<double>::value);
288   assert(!IsInteger<float>::value);
289   assert(!IsInteger<GlobalImp>::value);
290 }
291
292 static char* createStringWithContentsOfFile(const char* fileName)
293 {
294   char* buffer;
295   
296   int buffer_size = 0;
297   int buffer_capacity = 1024;
298   buffer = (char*)malloc(buffer_capacity);
299   
300   FILE* f = fopen(fileName, "r");
301   if (!f) {
302     fprintf(stderr, "Could not open file: %s\n", fileName);
303     return 0;
304   }
305   
306   while (!feof(f) && !ferror(f)) {
307     buffer_size += fread(buffer + buffer_size, 1, buffer_capacity - buffer_size, f);
308     if (buffer_size == buffer_capacity) { // guarantees space for trailing '\0'
309       buffer_capacity *= 2;
310       buffer = (char*)realloc(buffer, buffer_capacity);
311       assert(buffer);
312     }
313     
314     assert(buffer_size < buffer_capacity);
315   }
316   fclose(f);
317   buffer[buffer_size] = '\0';
318   
319   return buffer;
320 }