#!/usr/bin/env python
"""
Testris is the test runner for Learntris. Learntris is 
the part *you* write, in whatever language you prefer.

If you're seeing this message when you tried to run
testris.py, it means testris can't find your code!

Your first step is to write a *console-mode* program
(one that does absolutely nothing!) and tell testris
where to find it:

    ./testris.py [/path/to/learntris]

The path should refer to a physical file on disk, so if
you need command line arguments, create a wrapper program.
The default path is "./learntris".

Once testris is able to launch your program, this message
will be replaced with instructions for implementing your
first feature.
"""
from __future__ import print_function # let's keep it 3.x compatible
import sys, os, glob, subprocess, difflib, pprint, time
import extract

class Test(object):
    def __init__(self):
        self.desc = ""
        self.seq = [] # sequence of commands

    def __repr__(self):
        return "<Test: %s, %d commands>" %  (self.desc, len(self.seq))

class TestFailure(Exception):
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return self.msg

class TimeoutFailure(TestFailure):
    pass


def parse_test(lines):
    """
    :: [Str] -> [(Op, Str)] , where Op in { 'in', 'out' }
    """
    while lines and lines[-1].strip() == "": lines.pop()
    for line in lines:
        if line.startswith('#'): continue
        if '#' in line:                # strip trailing comments
            line = line[:line.find('#')]
        sline = line.strip()
        if sline == "":
            yield ('out', sline)
        elif sline[0] == '>':          # input to send
            yield ('in', sline[1:].strip())
        else:                          # expected output
            yield ('out', sline)

def spawn(program_name):
    return subprocess.Popen([program_name],
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE)


def await_results(program, timeout_seconds=2):
    """
    This polls the child program until it finishes
    executing. If it takes more than =timeout_seconds=,
    kill the program and raise a TimeoutFailure.
    """
    countdown = timeout_seconds * 10
    while program.poll() is None and countdown > 0:
        time.sleep(0.1) # seep for 1/10 of a second
        countdown -= 1
    if countdown == 0:
        program.kill()
        raise TimeoutFailure("<program timed out>")
    else: pass


def run_test(program, opcodes):
    # separate the test script into input and output lines:
    given    = [op[1] for op in opcodes if op[0] == 'in']
    expected = [op[1] for op in opcodes if op[0] == 'out']

    # send all the input lines:
    print("---- sending commands ----")
    for cmd in given:
        print(cmd)
        program.stdin.write(cmd + "\n")

    # let the program do its thing:
    print("---- expected results ----")
    for line in expected:
        print(line)

    print("---- awaiting results ----")
    await_results(program)

    # read all the actual output lines, and compare to expected:
    actual = [line.strip() for line in program.stdout.read().split("\n")]
    while actual and actual[-1] == "": actual.pop()
    if actual != expected:
        diff = list(difflib.Differ().compare(actual, expected))
        raise TestFailure('output mismatch:\n%s'
                          % pprint.pformat(diff))

def run_tests(program_name):
    for i, test in enumerate(extract.tests()):
        program = spawn(program_name)
        opcodes = list(parse_test(test.lines))
        print("Running test %d: %s" % (i+1, test.name))
        try:
            run_test(program, opcodes)
            print("Test %d passed" % (i+1))
        except (TimeoutFailure, TestFailure) as e:
            print("Test %d failed: %s" % (i+1, e))
            break
        print("\n") # add 2 blank lines between tests

def find_learntris():
    default = "./learntris"
    path = sys.argv[1] if len(sys.argv) == 2 else default
    if os.path.exists(path):
        result = path
    else:
        result = None
        if path == default:
            print(__doc__)
        else:
            print("Error: ('%s') not found." % path)
    return result


def main():
    path = find_learntris()
    if path: run_tests(path)

if __name__ == '__main__':
    main()

@@ -397,16 +397,28 @@
 arntris%5D
+ %5Barguments%5D
 %0A%0AThe pa
@@ -562,16 +562,98 @@
 tris%22.%0A%0A
+You can pass extra arguments that will be passed to the%0Aguest learntris program.%0A%0A
 Once tes
@@ -1920,28 +1920,28 @@
 awn(program_
-name
+args
 ):%0A    retur
@@ -1963,17 +1963,16 @@
 pen(
-%5B
 program_
 name
@@ -1971,13 +1971,12 @@
 ram_
-name%5D
+args
 ,%0A  
@@ -3535,20 +3535,20 @@
 program_
-name
+args
 ):%0A    f
@@ -3621,20 +3621,20 @@
 program_
-name
+args
 )%0A      
@@ -4056,19 +4056,27 @@
 s%22%0A    p
-ath
+rogram_args
  = sys.a
@@ -4080,16 +4080,17 @@
 s.argv%5B1
+:
 %5D if len
@@ -4100,17 +4100,17 @@
 s.argv) 
-=
+%3E
 = 2 else
@@ -4145,83 +4145,76 @@
 ts(p
-ath):%0A        result = path%0A    else:%0A        result = None%0A 
+rogram_args%5B0%5D):%0A        return program_args%0A%0A
     
-   
 if p
-ath
+rogram_args%5B0%5D
  == 
@@ -4222,20 +4222,16 @@
 efault:%0A
-    
         
@@ -4245,20 +4245,16 @@
 _doc__)%0A
-    
     else
@@ -4259,28 +4259,24 @@
 se:%0A        
-    
 print(%22Error
@@ -4299,19 +4299,30 @@
 nd.%22 %25 p
-ath
+rogram_args%5B0%5D
 )%0A    re
@@ -4326,22 +4326,20 @@
  return 
-result
+None
 %0A%0A%0Adef m
@@ -4350,19 +4350,27 @@
 ):%0A    p
-ath
+rogram_args
  = find_
@@ -4389,19 +4389,27 @@
     if p
-ath
+rogram_args
 : run_te
@@ -4413,19 +4413,27 @@
 _tests(p
-ath
+rogram_args
 )%0A%0Aif __

support passing command-line arguments to guest program