1# Copyright (c) 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import json
6import os
7import re
8import sys
9import shutil
10import tempfile
11import unittest
12
13sys.path.append(
14    os.path.join(os.path.dirname(__file__), '..', '..', 'mock'))
15import mock
16
17import vinn
18
19def _EscapeJsString(s):
20  return json.dumps(s)
21
22
23class VinnUnittest(unittest.TestCase):
24
25  @classmethod
26  def setUpClass(cls):
27    cls.test_data_dir = os.path.abspath(
28        os.path.join(os.path.dirname(__file__), 'test_data'))
29
30  def GetTestFilePath(self, file_name):
31    return os.path.join(self.test_data_dir, file_name)
32
33  def AssertHasNamedFrame(self, func_name, file_and_linum, exception_message):
34    m = re.search('at %s.+\(.*%s.*\)' % (func_name, file_and_linum),
35                  exception_message)
36    if not m:
37      sys.stderr.write('\n=============================================\n')
38      msg = "Expected to find %s and %s" % (func_name, file_and_linum)
39      sys.stderr.write('%s\n' % msg)
40      sys.stderr.write('=========== Begin Exception Message =========\n')
41      sys.stderr.write(exception_message)
42      sys.stderr.write('=========== End Exception Message =========\n\n')
43      self.fail(msg)
44
45  def AssertHasFrame(self, file_and_linum, exception_message):
46    m = re.search('at .*%s.*' % file_and_linum, exception_message)
47    if not m:
48      sys.stderr.write('\n=============================================\n')
49      msg = "Expected to find %s" % file_and_linum
50      sys.stderr.write('%s\n' % msg)
51      sys.stderr.write('=========== Begin Exception Message =========\n')
52      sys.stderr.write(exception_message)
53      sys.stderr.write('=========== End Exception Message =========\n\n')
54      self.fail(msg)
55
56  def testExecuteJsStringStdoutPiping(self):
57    tmp_dir = tempfile.mkdtemp()
58    try:
59      temp_file_name = os.path.join(tmp_dir, 'out_file')
60      with open(temp_file_name, 'w') as f:
61        vinn.ExecuteJsString(
62            'print("Hello w0rld");\n', stdout=f)
63      with open(temp_file_name, 'r') as f:
64        self.assertEquals(f.read(), 'Hello w0rld\n')
65    finally:
66      shutil.rmtree(tmp_dir)
67
68  def testRunJsStringStdoutPiping(self):
69    tmp_dir = tempfile.mkdtemp()
70    try:
71      temp_file_name = os.path.join(tmp_dir, 'out_file')
72      with open(temp_file_name, 'w') as f:
73        vinn.RunJsString(
74            'print("Hello w0rld");\n', stdout=f)
75      with open(temp_file_name, 'r') as f:
76        self.assertEquals(f.read(), 'Hello w0rld\n')
77    finally:
78      shutil.rmtree(tmp_dir)
79
80  def testExecuteFileStdoutPiping(self):
81    file_path = self.GetTestFilePath('simple.js')
82    tmp_dir = tempfile.mkdtemp()
83    try:
84      temp_file_name = os.path.join(tmp_dir, 'out_file')
85      with open(temp_file_name, 'w') as f:
86        vinn.ExecuteFile(file_path, stdout=f)
87      with open(temp_file_name, 'r') as f:
88        self.assertEquals(f.read(), 'Hello W0rld from simple.js\n')
89    finally:
90      shutil.rmtree(tmp_dir)
91
92  def testRunFileStdoutPiping(self):
93    file_path = self.GetTestFilePath('simple.js')
94    tmp_dir = tempfile.mkdtemp()
95    try:
96      temp_file_name = os.path.join(tmp_dir, 'out_file')
97      with open(temp_file_name, 'w') as f:
98        vinn.RunFile(file_path, stdout=f)
99      with open(temp_file_name, 'r') as f:
100        self.assertEquals(f.read(), 'Hello W0rld from simple.js\n')
101    finally:
102      shutil.rmtree(tmp_dir)
103
104  def testSimpleJsExecution(self):
105    file_path = self.GetTestFilePath('print_file_content.js')
106    dummy_test_path = self.GetTestFilePath('dummy_test_file')
107    output = vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir],
108                                   js_args=[dummy_test_path])
109    self.assertIn(
110        'This is file contains only data for testing.\n1 2 3 4', output)
111
112  def testDuplicateSourcePaths(self):
113    output = vinn.ExecuteJsString(
114      "HTMLImportsLoader.loadHTML('/load_simple_html.html');",
115      source_paths=[self.test_data_dir]*100)
116    self.assertIn(
117        'load_simple_html.html is loaded', output)
118
119  def testJsFileLoadHtmlFile(self):
120    file_path = self.GetTestFilePath('load_simple_html.js')
121    output = vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir])
122    expected_output = ('File foo.html is loaded\n'
123                       'x = 1\n'
124                       "File foo.html's second script is loaded\n"
125                       'x = 2\n'
126                       'load_simple_html.js is loaded\n')
127    self.assertEquals(output, expected_output)
128
129  def testJsFileLoadJsFile(self):
130    file_path = self.GetTestFilePath('load_simple_js.js')
131    output = vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir])
132    expected_output = ('bar.js is loaded\n'
133                       'load_simple_js.js is loaded\n')
134    self.assertEquals(output, expected_output)
135
136  def testHTMLFileLoadHTMLFile(self):
137    file_path = self.GetTestFilePath('load_simple_html.html')
138    output = vinn.ExecuteFile(
139        file_path, source_paths=[self.test_data_dir])
140    expected_output = ('File foo.html is loaded\n'
141                       'x = 1\n'
142                       "File foo.html's second script is loaded\n"
143                       'x = 2\n'
144                       'bar.js is loaded\n'
145                       'File load_simple_html.html is loaded\n')
146    self.assertEquals(output, expected_output)
147
148  def testQuit0Handling(self):
149    file_path = self.GetTestFilePath('quit_0_test.js')
150    res = vinn.RunFile(file_path, source_paths=[self.test_data_dir])
151    self.assertEquals(res.returncode, 0)
152
153  def testQuit1Handling(self):
154    file_path = self.GetTestFilePath('quit_1_test.js')
155    res = vinn.RunFile(file_path, source_paths=[self.test_data_dir])
156    self.assertEquals(res.returncode, 1)
157
158  def testQuit42Handling(self):
159    file_path = self.GetTestFilePath('quit_42_test.js')
160    res = vinn.RunFile(file_path, source_paths=[self.test_data_dir])
161    self.assertEquals(res.returncode, 42)
162
163  def testQuit274Handling(self):
164    file_path = self.GetTestFilePath('quit_274_test.js')
165    res = vinn.RunFile(file_path, source_paths=[self.test_data_dir])
166    self.assertEquals(res.returncode, 238)
167
168  def testErrorStackTraceJs(self):
169    file_path = self.GetTestFilePath('error_stack_test.js')
170    # error_stack_test.js imports load_simple_html.html
171    # load_simple_html.html imports foo.html
172    # foo.html imports error.js
173    # error.js defines maybeRaiseException() method that can raise exception
174    # foo.html defines maybeRaiseExceptionInFoo() method that calls
175    # maybeRaiseException()
176    # Finally, we call maybeRaiseExceptionInFoo() error_stack_test.js
177    # Exception log should capture these method calls' stack trace.
178    with self.assertRaises(RuntimeError) as context:
179      vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir])
180
181    # Assert error stack trace contain src files' info.
182    exception_message = context.exception.message
183    self.assertIn(
184        ('error.js:7: Error: Throw ERROR\n'
185         "    throw new Error('Throw ERROR');"), exception_message)
186    self.AssertHasNamedFrame('maybeRaiseException', 'error.js:7',
187                             exception_message)
188    self.AssertHasNamedFrame('global.maybeRaiseExceptionInFoo', 'foo.html:34',
189                             exception_message)
190    self.AssertHasFrame('error_stack_test.js:14', exception_message)
191
192  def testErrorStackTraceHTML(self):
193    file_path = self.GetTestFilePath('error_stack_test.html')
194    # error_stack_test.html imports error_stack_test.js
195    # error_stack_test.js imports load_simple_html.html
196    # load_simple_html.html imports foo.html
197    # foo.html imports error.js
198    # error.js defines maybeRaiseException() method that can raise exception
199    # foo.html defines maybeRaiseExceptionInFoo() method that calls
200    # maybeRaiseException()
201    # Finally, we call maybeRaiseExceptionInFoo() error_stack_test.js
202    # Exception log should capture these method calls' stack trace.
203    with self.assertRaises(RuntimeError) as context:
204      vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir])
205
206    # Assert error stack trace contain src files' info.
207    exception_message = context.exception.message
208    self.assertIn(
209        ('error.js:7: Error: Throw ERROR\n'
210         "    throw new Error('Throw ERROR');"), exception_message)
211
212    self.AssertHasNamedFrame('maybeRaiseException', 'error.js:7',
213                             exception_message)
214    self.AssertHasNamedFrame('global.maybeRaiseExceptionInFoo', 'foo.html:34',
215                             exception_message)
216    self.AssertHasFrame('error_stack_test.js:14', exception_message)
217    self.AssertHasNamedFrame('eval', 'error_stack_test.html:22',
218                             exception_message)
219
220  def testStackTraceOfErroWhenLoadingHTML(self):
221    file_path = self.GetTestFilePath('load_error.html')
222    with self.assertRaises(RuntimeError) as context:
223      vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir])
224
225    # Assert error stack trace contain src files' info.
226    exception_message = context.exception.message
227
228    self.assertIn('Error: /does_not_exist.html not found', exception_message)
229    self.AssertHasNamedFrame('eval', 'load_error_2.html:21', exception_message)
230    self.AssertHasNamedFrame('eval', 'load_error.html:23', exception_message)
231
232  def testStackTraceOfErroWhenSyntaxErrorOccurs(self):
233    file_path = self.GetTestFilePath('syntax_error.html')
234    with self.assertRaises(RuntimeError) as context:
235      vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir])
236
237    # Assert error stack trace contain src files' info.
238    exception_message = context.exception.message
239
240    self.assertIn('syntax_error.html:23: SyntaxError: Unexpected identifier',
241                  exception_message)
242
243  def testStackTraceOfErroWhenLoadingJS(self):
244    file_path = self.GetTestFilePath('load_js_error.html')
245    with self.assertRaises(RuntimeError) as context:
246      vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir])
247
248    # Assert error stack trace contain src files' info.
249    exception_message = context.exception.message
250
251    self.assertIn('Error: /does_not_exist.js not found', exception_message)
252    self.AssertHasNamedFrame('eval', 'load_js_error_2.html:20',
253                             exception_message)
254    self.AssertHasNamedFrame('eval', 'load_js_error.html:22',
255                             exception_message)
256
257  def testStrictError(self):
258    file_path = self.GetTestFilePath('non_strict_error.html')
259    with self.assertRaises(RuntimeError) as context:
260      vinn.ExecuteFile(file_path, source_paths=[self.test_data_dir])
261
262    # Assert error stack trace contain src files' info.
263    exception_message = context.exception.message
264
265    self.assertIn('non_defined_variable is not defined', exception_message)
266    self.AssertHasNamedFrame('eval', 'non_strict_error.html:17',
267                             exception_message)
268
269  def testConsolePolyfill(self):
270    self.assertEquals(
271        vinn.ExecuteJsString('console.log("hello", "world");'),
272        'hello world\n')
273    self.assertEquals(
274        vinn.ExecuteJsString('console.info("hello", "world");'),
275        'Info: hello world\n')
276    self.assertEquals(
277        vinn.ExecuteJsString('console.warn("hello", "world");'),
278        'Warning: hello world\n')
279    self.assertEquals(
280        vinn.ExecuteJsString('console.error("hello", "world");'),
281        'Error: hello world\n')
282
283  def testConsoleTimeEndAssertion(self):
284    file_path = self.GetTestFilePath('console_time_test.js')
285    try:
286        vinn.ExecuteFile(file_path)
287    except RuntimeError:
288      self.fail()
289
290  def testConsoleTime(self):
291    self.assertEquals(
292        vinn.ExecuteJsString('console.time("AA")'),
293        '')
294
295  def testConsoleTimeEndOutput(self):
296    output = vinn.ExecuteJsString('console.time("AA");console.timeEnd("AA")')
297    m = re.search('\d+\.\d+', output)
298    if not m:
299      sys.stderr.write('\nExpected to find output of timer AA')
300      self.fail()
301    a_duration = float(m.group())
302    self.assertTrue(a_duration > 0.0)
303    output = vinn.ExecuteJsString("""console.time("BB");
304                                     console.time("CC");
305                                     console.timeEnd("CC");
306                                     console.timeEnd("BB")""")
307    m = re.findall('(\d+\.\d+)', output)
308    if not m:
309      sys.stderr.write('\nExpected to find output of timer\n')
310      self.fail()
311    c_duration = float(m[0])
312    b_duration = float(m[1])
313    self.assertTrue(b_duration > c_duration)
314
315
316class PathUtilUnittest(unittest.TestCase):
317  def testPathUtil(self):
318    path_util_js_test = os.path.abspath(os.path.join(
319        os.path.dirname(__file__), 'path_utils_test.js'))
320    path_utils_js_dir = os.path.abspath(
321    os.path.join(os.path.dirname(__file__), 'path_utils.js'))
322
323    test_loading_js = """
324    load(%s);
325    load(%s);
326    runTests();
327    """ % (_EscapeJsString(path_utils_js_dir),
328           _EscapeJsString(path_util_js_test))
329
330    res = vinn.RunJsString(test_loading_js)
331    self.assertEquals(res.returncode, 0)
332
333
334def _GetLineNumberOfSubstring(content, substring):
335  """ Return the line number of |substring| in |content|."""
336  index = content.index(substring)
337  return content[:index].count('\n') + 1
338
339
340def _GenerateLineByLineDiff(actual, expected):
341  results = []
342  expected_lines = expected.split('\n')
343  actual_lines = actual.split('\n')
344  max_num_lines = max(len(expected_lines), len(actual_lines))
345  results.append('**Actual    : num lines =  %i' % len(actual_lines))
346  results.append('**Expected  : num lines = %i' % len(expected_lines))
347
348  for i in xrange(0, max_num_lines):
349    expected_current_line = expected_lines[i] if i < len(expected_lines) else ''
350    actual_current_line = actual_lines[i] if i < len(actual_lines) else ''
351    if actual_current_line == expected_current_line:
352      continue
353    results.append('================= Line %s ======================' % (i + 1))
354    results.append('**Actual    : %s' % repr(actual_current_line))
355    results.append('**Expected  : %s' % repr(expected_current_line))
356  return '\n'.join(results)
357
358
359class HTMLGeneratorTest(unittest.TestCase):
360
361  def AssertStringEquals(self, actual, expected):
362    if actual != expected:
363      message = 'Expected %s but got %s.\n' % (repr(expected), repr(actual))
364      message += _GenerateLineByLineDiff(actual, expected)
365      self.fail(message)
366
367  def GetGeneratedJs(self, html_text):
368    tmp_dir = tempfile.mkdtemp()
369    try:
370      temp_file_name = os.path.join(tmp_dir, 'test.html')
371      with open(temp_file_name, 'w') as f:
372        f.write(html_text)
373      return vinn.ExecuteJsString(
374          'write(generateJsFromHTML(read(%s)));' %
375          _EscapeJsString(temp_file_name))
376    finally:
377      shutil.rmtree(tmp_dir)
378
379  def testGenerateJsForD8RunnerSimpleHTMLImport(self):
380    html = '<link rel="import" href="/base/math.html">'
381    expected_js = "global.HTMLImportsLoader.loadHTML('/base/math.html');"
382    self.AssertStringEquals(self.GetGeneratedJs(html), expected_js)
383
384  def testGenerateJSForD8RunnerImportMultilineHTMLImport(self):
385    html = """
386          <link rel="import"
387          href="/base/math.html">"""
388    expected_js = "\nglobal.HTMLImportsLoader.loadHTML('/base/math.html');"
389    self.AssertStringEquals(self.GetGeneratedJs(html),
390                            expected_js)
391
392  def testGenerateJsForD8RunnerImportSimpleScriptWithSrc(self):
393    html = '<script src="/base/math.js"></script>'
394    expected_js = "global.HTMLImportsLoader.loadScript('/base/math.js');"
395    self.AssertStringEquals(self.GetGeneratedJs(html),
396                            expected_js)
397
398  def testGenerateJsForD8RunnerImportMultilineScriptWithSrc(self):
399    html = """<script
400                  type="text/javascript"
401                  src="/base/math.js">
402                  </script>"""
403    expected_js = """global.HTMLImportsLoader.loadScript('/base/math.js');
404
405
406                  """
407    self.AssertStringEquals(self.GetGeneratedJs(html),
408                            expected_js)
409
410  def testGenerateJsForD8RunnerWithMixedMultipleImport(self):
411    html = """
412<link rel="import" href="/base.html"><link rel="import" href="/base64.html">
413<link rel="import"
414            href="/base/math.html"><script
415            type="text/javascript"
416
417            src="/base/3d.js">
418            </script>
419
420<script src="/base/math.js"></script>
421
422 <link rel="import"
423  href="/base/random.html">
424"""
425    expected_js = ("""
426global.HTMLImportsLoader.loadHTML('/base.html');global.HTMLImportsLoader.loadHTML('/base64.html');
427global.HTMLImportsLoader.loadHTML('/base/math.html');
428global.HTMLImportsLoader.loadScript('/base/3d.js');
429
430
431
432            """ + """
433
434global.HTMLImportsLoader.loadScript('/base/math.js');
435
436global.HTMLImportsLoader.loadHTML('/base/random.html');""")
437    self.AssertStringEquals(self.GetGeneratedJs(html),
438                            expected_js)
439
440  def testGenerateJsForD8RunnerImportWithSimpleContent(self):
441    html = """<script>
442               var html_lines = [
443                '<script>',
444                '< /script>',
445               ];
446    </script>
447    """
448    expected_js = """
449               var html_lines = [
450                '<script>',
451                '< /script>',
452               ];
453    """
454    self.AssertStringEquals(self.GetGeneratedJs(html),
455                            expected_js)
456
457  def testGenerateJsForD8RunnerImportWithEscapedScriptTag(self):
458    html = """<script>
459var s = ("<") + "<\/script>";
460var x = 100;
461    </script>
462    """
463    expected_js = """
464var s = ("<") + "<\/script>";
465var x = 100;
466    """
467    self.AssertStringEquals(self.GetGeneratedJs(html),
468                            expected_js)
469
470  def testGenerateJsForD8RunnerImportWithSrcAndSimpleContent(self):
471    html = """<script
472               src="/base.js">var html_lines = [
473                '<script>',
474                '< /script>',
475               ];
476    </script>
477    """
478    expected_js = """global.HTMLImportsLoader.loadScript('/base.js');
479var html_lines = [
480                '<script>',
481                '< /script>',
482               ];
483    """
484    self.AssertStringEquals(self.GetGeneratedJs(html),
485                            expected_js)
486
487  def testGenerateJsForD8RunnerImportComplex(self):
488    html = """<!DOCTYPE html>
489<!--
490Copyright (c) 2014 The Chromium Authors. All rights reserved.
491Use of this source code is governed by a BSD-style license that can be
492found in the LICENSE file.
493-->
494        <link rel="import" href="/base/math.html"><script>var x = 1;</script>
495        <script src="/base/computer.js">
496          var linux = os.system;  // line number of this is 9
497        </script>
498        <link rel="import" href="/base/physics.html">
499
500        <script>
501              var html_lines = [
502                '<script>',
503                '< /script>',
504              ];
505              function foo() {
506                var y = [
507                  1,
508                  2,   // line number of this is 21
509                  3,
510                  4
511                ];
512              }
513        </script>
514
515         <link rel="import" href="/base/this_is_line_28.html">
516         <script>
517          var i = '<link rel="import" href="/base/math.html">';
518         </script>
519        """
520    expected_js = """
521
522
523
524
525
526global.HTMLImportsLoader.loadHTML('/base/math.html');var x = 1;
527global.HTMLImportsLoader.loadScript('/base/computer.js');
528          var linux = os.system;  // line number of this is 9
529        """ + """
530global.HTMLImportsLoader.loadHTML('/base/physics.html');
531
532
533              var html_lines = [
534                '<script>',
535                '< /script>',
536              ];
537              function foo() {
538                var y = [
539                  1,
540                  2,   // line number of this is 21
541                  3,
542                  4
543                ];
544              }
545        """ + """
546
547global.HTMLImportsLoader.loadHTML('/base/this_is_line_28.html');
548
549          var i = '<link rel="import" href="/base/math.html">';
550         """
551
552    generated_js = self.GetGeneratedJs(html)
553    self.AssertStringEquals(
554        _GetLineNumberOfSubstring(generated_js, '// line number of this is 9'),
555        9)
556    self.AssertStringEquals(
557        _GetLineNumberOfSubstring(generated_js, '// line number of this is 21'),
558        21)
559    self.AssertStringEquals(
560        _GetLineNumberOfSubstring(generated_js, 'this_is_line_28.html'),
561        28)
562    self.AssertStringEquals(generated_js, expected_js)
563
564
565class VinnV8ArgsTest(unittest.TestCase):
566
567  @classmethod
568  def setUpClass(cls):
569    cls.test_data_dir = os.path.abspath(
570        os.path.join(os.path.dirname(__file__), 'test_data'))
571
572  def GetTestFilePath(self, file_name):
573    return os.path.join(self.test_data_dir, file_name)
574
575  def setUp(self):
576    self.patcher = mock.patch('_vinn.subprocess.Popen')
577    self.mock_popen = self.patcher.start()
578    mock_rv = mock.Mock()
579    mock_rv.returncode = 0
580    # Communicate() returns [stdout, stderr]
581    mock_rv.communicate.return_value = ['', None]
582    self.mock_popen.return_value = mock_rv
583
584  def tearDown(self):
585    mock.patch.stopall()
586
587  def testRunJsStringWithV8Args(self):
588    vinn.RunJsString('var x = 1;', v8_args=['--foo', '--bar=True'])
589    v8_args = self.mock_popen.call_args[0][0]
590    self.assertIn('--foo', v8_args)
591    self.assertIn('--bar=True', v8_args)
592
593  def testExecuteJsStringWithV8Args(self):
594    vinn.ExecuteJsString('var x = 1;', v8_args=['--foo', '--bar=True'])
595    v8_args = self.mock_popen.call_args[0][0]
596    self.assertIn('--foo', v8_args)
597    self.assertIn('--bar=True', v8_args)
598
599  def testRunFileWithV8Args(self):
600    file_path = self.GetTestFilePath('simple.js')
601    vinn.RunFile(file_path, v8_args=['--foo', '--bar=True'])
602    v8_args = self.mock_popen.call_args[0][0]
603    self.assertIn('--foo', v8_args)
604
605  def testExecuteFileWithV8Args(self):
606    file_path = self.GetTestFilePath('simple.js')
607    vinn.ExecuteFile(file_path, v8_args=['--foo', '--bar=True'])
608    v8_args = self.mock_popen.call_args[0][0]
609    self.assertIn('--foo', v8_args)
610    self.assertIn('--bar=True', v8_args)
611