[swfinterp] Start working on basic tests
authorPhilipp Hagemeister <phihag@phihag.de>
Fri, 18 Jul 2014 12:20:34 +0000 (14:20 +0200)
committerPhilipp Hagemeister <phihag@phihag.de>
Sat, 19 Jul 2014 21:05:07 +0000 (23:05 +0200)
test/swftests/.gitignore [new file with mode: 0644]
test/swftests/LocalVars.as [new file with mode: 0644]
test/test_swfinterp.py [new file with mode: 0644]
youtube_dl/swfinterp.py

diff --git a/test/swftests/.gitignore b/test/swftests/.gitignore
new file mode 100644 (file)
index 0000000..da97ff7
--- /dev/null
@@ -0,0 +1 @@
+*.swf
diff --git a/test/swftests/LocalVars.as b/test/swftests/LocalVars.as
new file mode 100644 (file)
index 0000000..b2911a9
--- /dev/null
@@ -0,0 +1,13 @@
+// input: [1, 2]
+// output: 3
+
+package {
+public class LocalVars {
+    public static function main(a:int, b:int):int{
+        var c:int = a + b + b;
+        var d:int = c - b;
+        var e:int = d;
+        return e;
+    }
+}
+}
diff --git a/test/test_swfinterp.py b/test/test_swfinterp.py
new file mode 100644 (file)
index 0000000..98a14a0
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+# Allow direct execution
+import os
+import sys
+import unittest
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+
+import io
+import json
+import re
+import subprocess
+
+from youtube_dl.swfinterp import SWFInterpreter
+
+
+TEST_DIR = os.path.join(
+    os.path.dirname(os.path.abspath(__file__)), 'swftests')
+
+
+class TestSWFInterpreter(unittest.TestCase):
+    pass
+
+
+for testfile in os.listdir(TEST_DIR):
+    m = re.match(r'^(.*)\.(as)$', testfile)
+    if not m:
+        continue
+    test_id = m.group(1)
+
+    def test_func(self):
+        as_file = os.path.join(TEST_DIR, testfile)
+        swf_file = os.path.join(TEST_DIR, test_id + '.swf')
+        if ((not os.path.exists(swf_file))
+                or os.path.getmtime(swf_file) < os.path.getmtime(as_file)):
+            # Recompile
+            try:
+                subprocess.check_call(['mxmlc', '--output', swf_file, as_file])
+            except OSError as ose:
+                if ose.errno == errno.ENOENT:
+                    print('mxmlc not found! Skipping test.')
+                    return
+                raise
+
+        with open(swf_file, 'rb') as swf_f:
+            swf_content = swf_f.read()
+        swfi = SWFInterpreter(swf_content)
+
+        with io.open(as_file, 'r', encoding='utf-8') as as_f:
+            as_content = as_f.read()
+
+        def _find_spec(key):
+            m = re.search(
+                r'(?m)^//\s*%s:\s*(.*?)\n' % re.escape(key), as_content)
+            if not m:
+                raise ValueError('Cannot find %s in %s' % (key, testfile))
+            return json.loads(m.group(1))
+
+        input_args = _find_spec('input')
+        output = _find_spec('output')
+
+        swf_class = swfi.extract_class(test_id)
+        func = swfi.extract_function(swf_class, 'main')
+        res = func(input_args)
+        self.assertEqual(res, output)
+
+    test_func.__name__ = str('test_swf_' + test_id)
+    setattr(TestSWFInterpreter, test_func.__name__, test_func)
+
+
+if __name__ == '__main__':
+    unittest.main()
index 1cd2921386f3a86cc12a95545478311e571a268a..49fade364ac8963a713b46eccfcb8e9cd175e893 100644 (file)
@@ -8,8 +8,22 @@ import zlib
 from .utils import ExtractorError
 
 
-def _extract_tags(content):
-    pos = 0
+def _extract_tags(file_contents):
+    if file_contents[1:3] != b'WS':
+        raise ExtractorError(
+            'Not an SWF file; header is %r' % file_contents[:3])
+    if file_contents[:1] == b'C':
+        content = zlib.decompress(file_contents[8:])
+    else:
+        raise NotImplementedError(
+            'Unsupported compression format %r' %
+            file_contents[:1])
+
+    # Determine number of bits in framesize rectangle
+    framesize_nbits = struct.unpack('!B', content[:1])[0] >> 3
+    framesize_len = (5 + 4 * framesize_nbits + 7) // 8
+
+    pos = framesize_len + 2 + 2
     while pos < len(content):
         header16 = struct.unpack('<H', content[pos:pos + 2])[0]
         pos += 2
@@ -18,7 +32,9 @@ def _extract_tags(content):
         if tag_len == 0x3f:
             tag_len = struct.unpack('<I', content[pos:pos + 4])[0]
             pos += 4
-        assert pos + tag_len <= len(content)
+        assert pos + tag_len <= len(content), \
+            ('Tag %d ends at %d+%d - that\'s longer than the file (%d)'
+                % (tag_code, pos, tag_len, len(content)))
         yield (tag_code, content[pos:pos + tag_len])
         pos += tag_len
 
@@ -88,8 +104,7 @@ def _read_string(reader):
 
 
 def _read_bytes(count, reader):
-    if reader is None:
-        reader = code_reader
+    assert count >= 0
     resb = reader.read(count)
     assert len(resb) == count
     return resb
@@ -103,18 +118,8 @@ def _read_byte(reader):
 
 class SWFInterpreter(object):
     def __init__(self, file_contents):
-        if file_contents[1:3] != b'WS':
-            raise ExtractorError(
-                'Not an SWF file; header is %r' % file_contents[:3])
-        if file_contents[:1] == b'C':
-            content = zlib.decompress(file_contents[8:])
-        else:
-            raise NotImplementedError(
-                'Unsupported compression format %r' %
-                file_contents[:1])
-
         code_tag = next(tag
-                        for tag_code, tag in _extract_tags(content)
+                        for tag_code, tag in _extract_tags(file_contents)
                         if tag_code == 82)
         p = code_tag.index(b'\0', 4) + 1
         code_reader = io.BytesIO(code_tag[p:])
@@ -139,7 +144,7 @@ class SWFInterpreter(object):
         for _c in range(1, uint_count):
             u32()
         double_count = u30()
-        read_bytes((double_count - 1) * 8)
+        read_bytes(max(0, (double_count - 1)) * 8)
         string_count = u30()
         constant_strings = ['']
         for _c in range(1, string_count):
@@ -349,6 +354,9 @@ class SWFInterpreter(object):
                 elif opcode == 36:  # pushbyte
                     v = _read_byte(coder)
                     stack.append(v)
+                elif opcode == 42:  # dup
+                    value = stack[-1]
+                    stack.append(value)
                 elif opcode == 44:  # pushstring
                     idx = u30()
                     stack.append(constant_strings[idx])
@@ -468,10 +476,24 @@ class SWFInterpreter(object):
                         obj = stack.pop()
                         assert isinstance(obj, list)
                         stack.append(obj[idx])
+                elif opcode == 115:  # convert_
+                    value = stack.pop()
+                    intvalue = int(value)
+                    stack.append(intvalue)
                 elif opcode == 128:  # coerce
                     u30()
                 elif opcode == 133:  # coerce_s
                     assert isinstance(stack[-1], (type(None), compat_str))
+                elif opcode == 160:  # add
+                    value2 = stack.pop()
+                    value1 = stack.pop()
+                    res = value1 + value2
+                    stack.append(res)
+                elif opcode == 161:  # subtract
+                    value2 = stack.pop()
+                    value1 = stack.pop()
+                    res = value1 - value2
+                    stack.append(res)
                 elif opcode == 164:  # modulo
                     value2 = stack.pop()
                     value1 = stack.pop()