]>
Commit | Line | Data |
---|---|---|
cc52de43 | 1 | #!/usr/bin/env python3 |
54007a45 | 2 | |
9e3f1991 PH |
3 | # Allow direct execution |
4 | import os | |
5 | import sys | |
6 | import unittest | |
f8271158 | 7 | |
9e3f1991 PH |
8 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
9 | ||
be13a6e5 | 10 | import math |
11 | import re | |
54007a45 | 12 | |
be13a6e5 | 13 | from yt_dlp.jsinterp import JS_Undefined, JSInterpreter |
9e3f1991 PH |
14 | |
15 | ||
16 | class TestJSInterpreter(unittest.TestCase): | |
6f2287cb | 17 | def _test(self, code, ret, func='f', args=()): |
18 | self.assertEqual(JSInterpreter(code).call_function(func, *args), ret) | |
9e3f1991 | 19 | |
6f2287cb | 20 | def test_basic(self): |
21 | jsi = JSInterpreter('function f(){;}') | |
22 | self.assertEqual(repr(jsi.extract_function('f')), 'F<f>') | |
23 | self.assertEqual(jsi.call_function('f'), None) | |
8f53dc44 | 24 | |
6f2287cb | 25 | self._test('function f(){return 42;}', 42) |
26 | self._test('function f(){42}', None) | |
27 | self._test('var f = function(){return 42;}', 42) | |
ff29bf81 | 28 | |
9e3f1991 | 29 | def test_calc(self): |
6f2287cb | 30 | self._test('function f(a){return 2*a+1;}', 7, args=[3]) |
9e3f1991 PH |
31 | |
32 | def test_empty_return(self): | |
6f2287cb | 33 | self._test('function f(){return; y()}', None) |
9e3f1991 PH |
34 | |
35 | def test_morespace(self): | |
6f2287cb | 36 | self._test('function f (a) { return 2 * a + 1 ; }', 7, args=[3]) |
37 | self._test('function f () { x = 2 ; return x; }', 2) | |
9e3f1991 PH |
38 | |
39 | def test_strange_chars(self): | |
6f2287cb | 40 | self._test('function $_xY1 ($_axY1) { var $_axY2 = $_axY1 + 1; return $_axY2; }', |
41 | 21, args=[20], func='$_xY1') | |
9e3f1991 PH |
42 | |
43 | def test_operators(self): | |
6f2287cb | 44 | self._test('function f(){return 1 << 5;}', 32) |
45 | self._test('function f(){return 2 ** 5}', 32) | |
46 | self._test('function f(){return 19 & 21;}', 17) | |
47 | self._test('function f(){return 11 >> 2;}', 2) | |
48 | self._test('function f(){return []? 2+3: 4;}', 5) | |
49 | self._test('function f(){return 1 == 2}', False) | |
50 | self._test('function f(){return 0 && 1 || 2;}', 2) | |
51 | self._test('function f(){return 0 ?? 42;}', 0) | |
52 | self._test('function f(){return "life, the universe and everything" < 42;}', False) | |
1ac7f461 | 53 | |
9e3f1991 | 54 | def test_array_access(self): |
6f2287cb | 55 | self._test('function f(){var x = [1,2,3]; x[0] = 4; x[0] = 5; x[2.0] = 7; return x;}', [5, 2, 7]) |
9e3f1991 PH |
56 | |
57 | def test_parens(self): | |
6f2287cb | 58 | self._test('function f(){return (1) + (2) * ((( (( (((((3)))))) )) ));}', 7) |
59 | self._test('function f(){return (1 + 2) * 3;}', 9) | |
9e3f1991 | 60 | |
8f53dc44 | 61 | def test_quotes(self): |
6f2287cb | 62 | self._test(R'function f(){return "a\"\\("}', R'a"\(') |
8f53dc44 | 63 | |
9e3f1991 | 64 | def test_assignments(self): |
6f2287cb | 65 | self._test('function f(){var x = 20; x = 30 + 1; return x;}', 31) |
66 | self._test('function f(){var x = 20; x += 30 + 1; return x;}', 51) | |
67 | self._test('function f(){var x = 20; x -= 30 + 1; return x;}', -11) | |
9e3f1991 PH |
68 | |
69 | def test_comments(self): | |
3eff81fb PH |
70 | 'Skipping: Not yet fully implemented' |
71 | return | |
6f2287cb | 72 | self._test(''' |
73 | function f() { | |
74 | var x = /* 1 + */ 2; | |
75 | var y = /* 30 | |
76 | * 40 */ 50; | |
77 | return x + y; | |
78 | } | |
79 | ''', 52) | |
80 | ||
81 | self._test(''' | |
82 | function f() { | |
83 | var x = "/*"; | |
84 | var y = 1 /* comment */ + 2; | |
85 | return y; | |
86 | } | |
87 | ''', 3) | |
3eff81fb | 88 | |
9e3f1991 | 89 | def test_precedence(self): |
6f2287cb | 90 | self._test(''' |
91 | function f() { | |
92 | var a = [10, 20, 30, 40, 50]; | |
93 | var b = 6; | |
94 | a[0]=a[b%a.length]; | |
95 | return a; | |
96 | } | |
97 | ''', [20, 20, 30, 40, 50]) | |
9e3f1991 | 98 | |
49b4ceae | 99 | def test_builtins(self): |
6f2287cb | 100 | jsi = JSInterpreter('function f() { return NaN }') |
101 | self.assertTrue(math.isnan(jsi.call_function('f'))) | |
d81ba7d4 | 102 | |
6f2287cb | 103 | self._test('function f() { return new Date("Wednesday 31 December 1969 18:01:26 MDT") - 0; }', |
104 | 86000) | |
105 | self._test('function f(dt) { return new Date(dt) - 0; }', | |
106 | 86000, args=['Wednesday 31 December 1969 18:01:26 MDT']) | |
49b4ceae | 107 | |
189935f1 KM |
108 | def test_call(self): |
109 | jsi = JSInterpreter(''' | |
6f2287cb | 110 | function x() { return 2; } |
111 | function y(a) { return x() + (a?a:0); } | |
112 | function z() { return y(3); } | |
189935f1 KM |
113 | ''') |
114 | self.assertEqual(jsi.call_function('z'), 5) | |
8f53dc44 | 115 | self.assertEqual(jsi.call_function('y'), 2) |
9e3f1991 | 116 | |
8b008d62 | 117 | def test_if(self): |
6f2287cb | 118 | self._test(''' |
119 | function f() { | |
120 | let a = 9; | |
121 | if (0==0) {a++} | |
122 | return a | |
123 | } | |
124 | ''', 10) | |
125 | ||
126 | self._test(''' | |
127 | function f() { | |
128 | if (0==0) {return 10} | |
129 | } | |
130 | ''', 10) | |
131 | ||
132 | self._test(''' | |
133 | function f() { | |
134 | if (0!=0) {return 1} | |
135 | else {return 10} | |
136 | } | |
137 | ''', 10) | |
8b008d62 | 138 | |
139 | """ # Unsupported | |
6f2287cb | 140 | self._test(''' |
141 | function f() { | |
142 | if (0!=0) {return 1} | |
143 | else if (1==0) {return 2} | |
144 | else {return 10} | |
145 | } | |
146 | ''', 10) | |
8b008d62 | 147 | """ |
148 | ||
404f611f | 149 | def test_for_loop(self): |
6f2287cb | 150 | self._test('function f() { a=0; for (i=0; i-10; i++) {a++} return a }', 10) |
404f611f | 151 | |
152 | def test_switch(self): | |
153 | jsi = JSInterpreter(''' | |
6f2287cb | 154 | function f(x) { switch(x){ |
155 | case 1:x+=1; | |
156 | case 2:x+=2; | |
157 | case 3:x+=3;break; | |
158 | case 4:x+=4; | |
159 | default:x=0; | |
160 | } return x } | |
404f611f | 161 | ''') |
6f2287cb | 162 | self.assertEqual(jsi.call_function('f', 1), 7) |
163 | self.assertEqual(jsi.call_function('f', 3), 6) | |
164 | self.assertEqual(jsi.call_function('f', 5), 0) | |
404f611f | 165 | |
a1fc7ca0 | 166 | def test_switch_default(self): |
167 | jsi = JSInterpreter(''' | |
6f2287cb | 168 | function f(x) { switch(x){ |
169 | case 2: x+=2; | |
170 | default: x-=1; | |
171 | case 5: | |
172 | case 6: x+=6; | |
173 | case 0: break; | |
174 | case 1: x+=1; | |
175 | } return x } | |
a1fc7ca0 | 176 | ''') |
6f2287cb | 177 | self.assertEqual(jsi.call_function('f', 1), 2) |
178 | self.assertEqual(jsi.call_function('f', 5), 11) | |
179 | self.assertEqual(jsi.call_function('f', 9), 14) | |
a1fc7ca0 | 180 | |
404f611f | 181 | def test_try(self): |
6f2287cb | 182 | self._test('function f() { try{return 10} catch(e){return 5} }', 10) |
404f611f | 183 | |
1ac7f461 | 184 | def test_catch(self): |
6f2287cb | 185 | self._test('function f() { try{throw 10} catch(e){return 5} }', 5) |
1ac7f461 | 186 | |
187 | def test_finally(self): | |
6f2287cb | 188 | self._test('function f() { try{throw 10} finally {return 42} }', 42) |
189 | self._test('function f() { try{throw 10} catch(e){return 5} finally {return 42} }', 42) | |
1ac7f461 | 190 | |
191 | def test_nested_try(self): | |
6f2287cb | 192 | self._test(''' |
193 | function f() {try { | |
194 | try{throw 10} finally {throw 42} | |
195 | } catch(e){return 5} } | |
196 | ''', 5) | |
1ac7f461 | 197 | |
404f611f | 198 | def test_for_loop_continue(self): |
6f2287cb | 199 | self._test('function f() { a=0; for (i=0; i-10; i++) { continue; a++ } return a }', 0) |
404f611f | 200 | |
201 | def test_for_loop_break(self): | |
6f2287cb | 202 | self._test('function f() { a=0; for (i=0; i-10; i++) { break; a++ } return a }', 0) |
404f611f | 203 | |
1ac7f461 | 204 | def test_for_loop_try(self): |
6f2287cb | 205 | self._test(''' |
206 | function f() { | |
207 | for (i=0; i-10; i++) { try { if (i == 5) throw i} catch {return 10} finally {break} }; | |
208 | return 42 } | |
209 | ''', 42) | |
1ac7f461 | 210 | |
404f611f | 211 | def test_literal_list(self): |
6f2287cb | 212 | self._test('function f() { return [1, 2, "asdf", [5, 6, 7]][3] }', [5, 6, 7]) |
404f611f | 213 | |
214 | def test_comma(self): | |
6f2287cb | 215 | self._test('function f() { a=5; a -= 1, a+=3; return a }', 7) |
216 | self._test('function f() { a=5; return (a -= 1, a+=3, a); }', 7) | |
217 | self._test('function f() { return (l=[0,1,2,3], function(a, b){return a+b})((l[1], l[2]), l[3]) }', 5) | |
6d3e7424 | 218 | |
49b4ceae | 219 | def test_void(self): |
6f2287cb | 220 | self._test('function f() { return void 42; }', None) |
49b4ceae | 221 | |
8f53dc44 | 222 | def test_return_function(self): |
223 | jsi = JSInterpreter(''' | |
6f2287cb | 224 | function f() { return [1, function(){return 1}][1] } |
8f53dc44 | 225 | ''') |
6f2287cb | 226 | self.assertEqual(jsi.call_function('f')([]), 1) |
8f53dc44 | 227 | |
be13a6e5 | 228 | def test_null(self): |
6f2287cb | 229 | self._test('function f() { return null; }', None) |
230 | self._test('function f() { return [null > 0, null < 0, null == 0, null === 0]; }', | |
231 | [False, False, False, False]) | |
232 | self._test('function f() { return [null >= 0, null <= 0]; }', [True, True]) | |
be13a6e5 | 233 | |
234 | def test_undefined(self): | |
6f2287cb | 235 | self._test('function f() { return undefined === undefined; }', True) |
236 | self._test('function f() { return undefined; }', JS_Undefined) | |
237 | self._test('function f() {return undefined ?? 42; }', 42) | |
238 | self._test('function f() { let v; return v; }', JS_Undefined) | |
239 | self._test('function f() { let v; return v**0; }', 1) | |
240 | self._test('function f() { let v; return [v>42, v<=42, v&&42, 42&&v]; }', | |
241 | [False, False, JS_Undefined, JS_Undefined]) | |
242 | ||
243 | self._test(''' | |
244 | function f() { return [ | |
245 | undefined === undefined, | |
246 | undefined == undefined, | |
247 | undefined == null, | |
248 | undefined < undefined, | |
249 | undefined > undefined, | |
250 | undefined === 0, | |
251 | undefined == 0, | |
252 | undefined < 0, | |
253 | undefined > 0, | |
254 | undefined >= 0, | |
255 | undefined <= 0, | |
256 | undefined > null, | |
257 | undefined < null, | |
258 | undefined === null | |
259 | ]; } | |
260 | ''', list(map(bool, (1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)))) | |
261 | ||
262 | jsi = JSInterpreter(''' | |
263 | function f() { let v; return [42+v, v+42, v**42, 42**v, 0**v]; } | |
264 | ''') | |
265 | for y in jsi.call_function('f'): | |
be13a6e5 | 266 | self.assertTrue(math.isnan(y)) |
267 | ||
be13a6e5 | 268 | def test_object(self): |
6f2287cb | 269 | self._test('function f() { return {}; }', {}) |
270 | self._test('function f() { let a = {m1: 42, m2: 0 }; return [a["m1"], a.m2]; }', [42, 0]) | |
271 | self._test('function f() { let a; return a?.qq; }', JS_Undefined) | |
272 | self._test('function f() { let a = {m1: 42, m2: 0 }; return a?.qq; }', JS_Undefined) | |
be13a6e5 | 273 | |
274 | def test_regex(self): | |
6f2287cb | 275 | self._test('function f() { let a=/,,[/,913,/](,)}/; }', None) |
be13a6e5 | 276 | |
6f2287cb | 277 | jsi = JSInterpreter('function f() { let a=/,,[/,913,/](,)}/; return a; }') |
278 | self.assertIsInstance(jsi.call_function('f'), re.Pattern) | |
be13a6e5 | 279 | |
6f2287cb | 280 | jsi = JSInterpreter('function f() { let a=/,,[/,913,/](,)}/i; return a; }') |
281 | self.assertEqual(jsi.call_function('f').flags & re.I, re.I) | |
be13a6e5 | 282 | |
6f2287cb | 283 | jsi = JSInterpreter(R'function f() { let a=/,][}",],()}(\[)/; return a; }') |
284 | self.assertEqual(jsi.call_function('f').pattern, r',][}",],()}(\[)') | |
05deb747 | 285 | |
6f2287cb | 286 | jsi = JSInterpreter(R'function f() { let a=[/[)\\]/]; return a[0]; }') |
287 | self.assertEqual(jsi.call_function('f').pattern, r'[)\\]') | |
0468a3b3 | 288 | |
f26af78a | 289 | def test_char_code_at(self): |
6f2287cb | 290 | jsi = JSInterpreter('function f(i){return "test".charCodeAt(i)}') |
291 | self.assertEqual(jsi.call_function('f', 0), 116) | |
292 | self.assertEqual(jsi.call_function('f', 1), 101) | |
293 | self.assertEqual(jsi.call_function('f', 2), 115) | |
294 | self.assertEqual(jsi.call_function('f', 3), 116) | |
295 | self.assertEqual(jsi.call_function('f', 4), None) | |
296 | self.assertEqual(jsi.call_function('f', 'not_a_number'), 116) | |
f26af78a E |
297 | |
298 | def test_bitwise_operators_overflow(self): | |
6f2287cb | 299 | self._test('function f(){return -524999584 << 5}', 379882496) |
300 | self._test('function f(){return 1236566549 << 5}', 915423904) | |
f26af78a | 301 | |
1d765618 | 302 | def test_bitwise_operators_typecast(self): |
6f2287cb | 303 | self._test('function f(){return null << 5}', 0) |
304 | self._test('function f(){return undefined >> 5}', 0) | |
305 | self._test('function f(){return 42 << NaN}', 42) | |
1d765618 | 306 | |
7cf51f21 | 307 | def test_negative(self): |
6f2287cb | 308 | self._test('function f(){return 2 * -2.0 ;}', -4) |
309 | self._test('function f(){return 2 - - -2 ;}', 0) | |
310 | self._test('function f(){return 2 - - - -2 ;}', 4) | |
311 | self._test('function f(){return 2 - + + - -2;}', 0) | |
312 | self._test('function f(){return 2 + - + - -2;}', 0) | |
7cf51f21 | 313 | |
582be358 | 314 | |
9e3f1991 PH |
315 | if __name__ == '__main__': |
316 | unittest.main() |