diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c5bf95eaefe34f4e8603cd40184f0be6ab9ca44d
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,31 @@
+image: ubuntu:latest
+
+stages:
+  - test
+
+.test_snipper:
+  stage: test
+  script:
+    - echo "Testing student files"
+#    - pwd
+    - apt-get update -y
+    - apt install -y python3-pip
+    - apt install -y python3.10 python-is-python3
+#    - apt install -y python-is-python3
+#    - python --version
+#    - locate python
+    - apt install -y git
+    - apt install -y xvfb # Virtual framebuffer for GL stuff.
+#    - apt install -y inkscape pdftk
+#    - apt install -y latexmk  poppler-utils # latexmk and pdftocairo
+#    - DEBIAN_FRONTEND='noninteractive' apt install -y texlive-latex-recommended texlive-latex-extra texlive-science pdf2svg
+#    - pip install -U Pillow
+    - pip install -e ./
+    - cd tests
+    - xvfb-run -s "-screen 0 1400x900x24" python -m unittest test_python.py
+    - latexmk --version
+    - ls tests_images/*
+
+test_39:
+  extends: .test_students
+  image: ubuntu:latest
diff --git a/tests/demo1/ex1.py b/tests/demo1/ex1.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/demo1/tree.py b/tests/demo1/tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..9dcb9eb89958d2151e809d320cd33a8a778be565
--- /dev/null
+++ b/tests/demo1/tree.py
@@ -0,0 +1,46 @@
+import sys
+
+class TreeNode(object): #!s=a
+    def __init__(self, x): #!s=a
+        self.val = x #!b=a
+        self.left = None
+        self.right = None #!b=a
+
+#!s=a
+def sorted_array_to_bst(nums):
+    if not nums: #!b=cuthere
+        return None #!s=myfile
+    mid_val = len(nums) // 2 #!s=a
+    node = TreeNode(nums[mid_val]) #!b=cuthere "Your stuff here"
+    node.left = sorted_array_to_bst(nums[:mid_val])
+    node.right = sorted_array_to_bst(nums[mid_val + 1:]) #!b # Solve this problem
+    return node #!b Here
+print("hello world asdfasd")
+
+#!o=a
+def preOrder(node):
+    if not node: #!f
+        return
+    print(node.val)
+    preOrder(node.left)#!s=myfile
+    preOrder(node.right)
+for _ in range(10):
+    print("Hello world")
+#!o=a
+a = 234
+
+result = sorted_array_to_bst([1, 2, 3, 4, 5, 6, 7])
+preOrder(result)
+
+#!i=a
+for _ in range(10):
+    print("hi")
+#!i=a
+
+#!i=b
+print("hello")
+def myfun(a):
+    return a*2
+
+print(myfun(4))
+#!i=b
\ No newline at end of file
diff --git a/tests/demo1_correct/code/ex1.py b/tests/demo1_correct/code/ex1.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/demo1_correct/code/tree.py b/tests/demo1_correct/code/tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4ad1f7467fcdc48c1708adc9cc99911d57e88c0
--- /dev/null
+++ b/tests/demo1_correct/code/tree.py
@@ -0,0 +1,45 @@
+import sys
+
+class TreeNode(object): 
+    def __init__(self, x): 
+        # TODO: 3 lines missing.
+        raise NotImplementedError("Insert your solution and remove this error.")
+
+#!s=a
+def sorted_array_to_bst(nums):
+    # TODO: 4 lines missing.
+    raise NotImplementedError("Your stuff here")
+    node.left = sorted_array_to_bst(nums[:mid_val])
+    # Solve this problem
+    # TODO: 2 lines missing.
+    raise NotImplementedError("Here")
+print("hello world asdfasd")
+
+#!o=a
+def preOrder(node):
+    if not node: 
+        # TODO: 1 lines missing.
+        raise NotImplementedError("Implement function body")
+    print(node.val)
+    preOrder(node.left)
+    preOrder(node.right)
+for _ in range(10):
+    print("Hello world")
+#!o=a
+a = 234
+
+result = sorted_array_to_bst([1, 2, 3, 4, 5, 6, 7])
+preOrder(result)
+
+#!i=a
+for _ in range(10):
+    print("hi")
+#!i=a
+
+#!i=b
+print("hello")
+def myfun(a):
+    return a*2
+
+print(myfun(4))
+#!i=b
diff --git a/tests/demo1_correct/output/tree_a.py b/tests/demo1_correct/output/tree_a.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3e2198a7426b41ea2b0c6527a271d008de63179
--- /dev/null
+++ b/tests/demo1_correct/output/tree_a.py
@@ -0,0 +1,8 @@
+# tree.py
+class TreeNode(object): 
+    def __init__(self, x): 
+
+def sorted_array_to_bst(nums):
+    if not nums: 
+        return None 
+    mid_val = len(nums) // 2 
\ No newline at end of file
diff --git a/tests/demo1_correct/output/tree_a.shell b/tests/demo1_correct/output/tree_a.shell
new file mode 100644
index 0000000000000000000000000000000000000000..f1a6a456de100126a19878a15d33013bfceccef5
--- /dev/null
+++ b/tests/demo1_correct/output/tree_a.shell
@@ -0,0 +1,3 @@
+>>>
+>>> for _ in range(10):
+...     print("hi")
\ No newline at end of file
diff --git a/tests/demo1_correct/output/tree_a.txt b/tests/demo1_correct/output/tree_a.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ed0f59ced5157eb2fb79d4dcbc06e7c0e4a3bff8
--- /dev/null
+++ b/tests/demo1_correct/output/tree_a.txt
@@ -0,0 +1,10 @@
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
diff --git a/tests/demo1_correct/output/tree_b.shell b/tests/demo1_correct/output/tree_b.shell
new file mode 100644
index 0000000000000000000000000000000000000000..922b7549fe26677c3f4f3ad83cefa333527d74ec
--- /dev/null
+++ b/tests/demo1_correct/output/tree_b.shell
@@ -0,0 +1,7 @@
+>>>
+>>>
+>>> print("hello")
+hello
+>>> def myfun(a):
+...     return a*2
+...
\ No newline at end of file
diff --git a/tests/demo1_correct/output/tree_myfile.py b/tests/demo1_correct/output/tree_myfile.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea738ec9b82bee9e93a468c502f1ff17f060bacf
--- /dev/null
+++ b/tests/demo1_correct/output/tree_myfile.py
@@ -0,0 +1,15 @@
+# tree.py
+        return None 
+    mid_val = len(nums) // 2 
+    node = TreeNode(nums[mid_val]) 
+    node.left = sorted_array_to_bst(nums[:mid_val])
+    node.right = sorted_array_to_bst(nums[mid_val + 1:]) 
+    return node 
+print("hello world asdfasd")
+
+#!o=a
+def preOrder(node):
+    if not node: 
+        return
+    print(node.val)
+    preOrder(node.left)
\ No newline at end of file
diff --git a/tests/demo1_tmp/code/ex1.py b/tests/demo1_tmp/code/ex1.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/demo1_tmp/code/tree.py b/tests/demo1_tmp/code/tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4ad1f7467fcdc48c1708adc9cc99911d57e88c0
--- /dev/null
+++ b/tests/demo1_tmp/code/tree.py
@@ -0,0 +1,45 @@
+import sys
+
+class TreeNode(object): 
+    def __init__(self, x): 
+        # TODO: 3 lines missing.
+        raise NotImplementedError("Insert your solution and remove this error.")
+
+#!s=a
+def sorted_array_to_bst(nums):
+    # TODO: 4 lines missing.
+    raise NotImplementedError("Your stuff here")
+    node.left = sorted_array_to_bst(nums[:mid_val])
+    # Solve this problem
+    # TODO: 2 lines missing.
+    raise NotImplementedError("Here")
+print("hello world asdfasd")
+
+#!o=a
+def preOrder(node):
+    if not node: 
+        # TODO: 1 lines missing.
+        raise NotImplementedError("Implement function body")
+    print(node.val)
+    preOrder(node.left)
+    preOrder(node.right)
+for _ in range(10):
+    print("Hello world")
+#!o=a
+a = 234
+
+result = sorted_array_to_bst([1, 2, 3, 4, 5, 6, 7])
+preOrder(result)
+
+#!i=a
+for _ in range(10):
+    print("hi")
+#!i=a
+
+#!i=b
+print("hello")
+def myfun(a):
+    return a*2
+
+print(myfun(4))
+#!i=b
diff --git a/tests/demo1_tmp/output/tree_a.py b/tests/demo1_tmp/output/tree_a.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3e2198a7426b41ea2b0c6527a271d008de63179
--- /dev/null
+++ b/tests/demo1_tmp/output/tree_a.py
@@ -0,0 +1,8 @@
+# tree.py
+class TreeNode(object): 
+    def __init__(self, x): 
+
+def sorted_array_to_bst(nums):
+    if not nums: 
+        return None 
+    mid_val = len(nums) // 2 
\ No newline at end of file
diff --git a/tests/demo1_tmp/output/tree_a.shell b/tests/demo1_tmp/output/tree_a.shell
new file mode 100644
index 0000000000000000000000000000000000000000..f1a6a456de100126a19878a15d33013bfceccef5
--- /dev/null
+++ b/tests/demo1_tmp/output/tree_a.shell
@@ -0,0 +1,3 @@
+>>>
+>>> for _ in range(10):
+...     print("hi")
\ No newline at end of file
diff --git a/tests/demo1_tmp/output/tree_a.txt b/tests/demo1_tmp/output/tree_a.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ed0f59ced5157eb2fb79d4dcbc06e7c0e4a3bff8
--- /dev/null
+++ b/tests/demo1_tmp/output/tree_a.txt
@@ -0,0 +1,10 @@
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
+Hello world
diff --git a/tests/demo1_tmp/output/tree_b.shell b/tests/demo1_tmp/output/tree_b.shell
new file mode 100644
index 0000000000000000000000000000000000000000..922b7549fe26677c3f4f3ad83cefa333527d74ec
--- /dev/null
+++ b/tests/demo1_tmp/output/tree_b.shell
@@ -0,0 +1,7 @@
+>>>
+>>>
+>>> print("hello")
+hello
+>>> def myfun(a):
+...     return a*2
+...
\ No newline at end of file
diff --git a/tests/demo1_tmp/output/tree_myfile.py b/tests/demo1_tmp/output/tree_myfile.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea738ec9b82bee9e93a468c502f1ff17f060bacf
--- /dev/null
+++ b/tests/demo1_tmp/output/tree_myfile.py
@@ -0,0 +1,15 @@
+# tree.py
+        return None 
+    mid_val = len(nums) // 2 
+    node = TreeNode(nums[mid_val]) 
+    node.left = sorted_array_to_bst(nums[:mid_val])
+    node.right = sorted_array_to_bst(nums[mid_val + 1:]) 
+    return node 
+print("hello world asdfasd")
+
+#!o=a
+def preOrder(node):
+    if not node: 
+        return
+    print(node.val)
+    preOrder(node.left)
\ No newline at end of file
diff --git a/tests/setup_test_files.py b/tests/setup_test_files.py
new file mode 100644
index 0000000000000000000000000000000000000000..affbfaa4c4f758f08a929278ca7345ae1a41c211
--- /dev/null
+++ b/tests/setup_test_files.py
@@ -0,0 +1,38 @@
+import os.path
+import shutil
+from snipper import snip_dir
+
+def setup(source, dest):
+    if os.path.isdir(dest):
+        shutil.rmtree(dest)
+    os.mkdir(dest)
+    os.mkdir(dest + "/output")
+
+    snip_dir(source, dest_dir=dest + "/code", clean_destination_dir=True, output_dir=dest + "/output")
+
+
+if __name__ == "__main__":
+    print("File used for setting up sets -- don't run this script unless repository known to work (Tue, 2022)")
+
+    if os.path.isdir("student_repo"):
+        shutil.rmtree("student_repo")
+
+    from snipper import snip_dir
+
+    # if not os.path.isdir("student_correct_output"):
+    #     os.mkdir("student_correct_output")
+
+
+
+    setup("demo1", "demo1_correct")
+
+    def compare(source, dest):
+
+
+
+        pass
+
+
+    a= 234
+    # assert False
+
diff --git a/tests/test_python.py b/tests/test_python.py
new file mode 100644
index 0000000000000000000000000000000000000000..30c99ce5a6cd4cdec36c90dcbd2a8485d3aeed7e
--- /dev/null
+++ b/tests/test_python.py
@@ -0,0 +1,42 @@
+
+from unittest import TestCase
+import filecmp
+
+class dircmp(filecmp.dircmp):
+    """
+    Compare the content of dir1 and dir2. In contrast with filecmp.dircmp, this
+    subclass compares the content of files with the same path.
+    """
+    def phase3(self):
+        """
+        Find out differences between common files.
+        Ensure we are using content comparison with shallow=False.
+        """
+        fcomp = filecmp.cmpfiles(self.left, self.right, self.common_files,
+                                 shallow=False)
+        self.same_files, self.diff_files, self.funny_files = fcomp
+
+import os.path
+
+def is_same(dir1, dir2):
+    """
+    Compare two directory trees content.
+    Return False if they differ, True is they are the same.
+    """
+    compared = dircmp(dir1, dir2)
+    if (compared.left_only or compared.right_only or compared.diff_files
+        or compared.funny_files):
+        return False
+    for subdir in compared.common_dirs:
+        if not is_same(os.path.join(dir1, subdir), os.path.join(dir2, subdir)):
+            return False
+    return True
+
+class TestPython(TestCase):
+    def test_demo1(self):
+        from setup_test_files import setup
+        setup("demo1", "demo1_tmp")
+        import filecmp
+        report = filecmp.dircmp("demo1_correct", "demo1_tmp")
+        print("Different", report.report())
+        self.assertTrue(is_same("demo1_correct", "demo1_tmp"))