import unittest
from gradescope_utils.autograder_utils.decorators import number, weight
import subprocess
from time import sleep
from re import sub
from utils import *
import signal
import re

def nonEmptyLine(s):
        return len(s.strip()) != 0
    
def stripstr(s):
    return s.strip()

def removeEmptyLines(text):
    lst = filter(nonEmptyLine, list(map(stripstr, text.split("\n"))))
    return "\n".join(list(lst))

class RuntimeAbort(Exception):
    pass

class RuntimeSegFault(Exception):
    pass

class RuntimeFPE(Exception):
    pass

class RuntimeBusError(Exception):
    pass

class RuntimeIllegalInstruction(Exception):
    pass

class MakefileError(Exception):
    pass

def checkRuntimeErrors(proc):
    if (proc.returncode == -signal.SIGABRT):
        raise RuntimeAbort
    elif (proc.returncode == -signal.SIGSEGV):
        raise RuntimeSegFault
    elif (proc.returncode == -signal.SIGFPE):
        raise RuntimeFPE
    elif (proc.returncode == -signal.SIGBUS):
        raise RuntimeBusError
    elif (proc.returncode == -signal.SIGILL):
        raise RuntimeIllegalInstruction
    elif (proc.returncode == 2):
        raise MakefileError

class TestDiff(unittest.TestCase):
    maxDiff = None
    
    # Array of all the expected file names
    files = ['makefile', 'mainDriver.c', 'getHeader.c', 'writeHeader.c', 'initInputImageArray.c', 'manip.c', 'writePixels.c', 'defs.h']
    
    def setUp(self):
        pass

    # Associated test number within Gradescope
    @number("1")
    # Associated point value within Gradescope
    @weight(5)
    def test_checkFiles(self):
        """Ensure all required files are present"""
        
        checkFiles(self.files)
        sleep(1)
    
    # Associated test number within Gradescope
    @number("2")
    # Associated point value within Gradescope
    @weight(2.5)
    def test_Compile(self):
        # Title used by Gradescope 
        """Clean compile"""

        # Create a subprocess to run the students make file to ensure it compiles
        test = subprocess.Popen(["make"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        output = test.stderr.read().strip().decode('utf-8')
        test.kill()

        # Standard unit test case with an associated error message
        self.longMessage = False
        self.assertTrue(output == "", msg=output)
        test.terminate()

    # Associated test number within Gradescope
    @number("3")
    # Associated point value within Gradescope
    @weight(2.5)
    def test_InputSequence1234(self):
        # Title used by Gradescope 
        """Check that input sequence "1, 2, 3, 4" results in correct menu output"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
            
            # Try to decode stdout
            try:
                stdout = stdout.strip().decode('utf-8')
                stderr = stderr.strip().decode('utf-8')
                test.kill()
                
                # Open reference output and decode
                reference = open('reference/1234.txt', 'rb').read().strip().decode('utf-8')
                
                # Remove empty lines from both output and reference
                stdout = removeEmptyLines(stdout)
                stderr = removeEmptyLines(stderr)
                reference = removeEmptyLines(reference)
                
                # Check if stdout is empty but stderr has content
                if not stdout and stderr:
                    # Check that stderr and reference are equal
                    self.assertEqual(stderr, reference, msg='Program output does not match expected output.')
                # If stderr is not usable for comparison, test with stdout
                else:
                    # Check that stdout and reference are equal
                    self.assertEqual(stdout, reference, msg='Program output does not match expected output.')
            # Catch exception for decode error
            except (UnicodeDecodeError):
                test.kill()
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
                self.longMessage = False
                self.assertTrue(correct, msg)
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
            self.longMessage = False
            self.assertTrue(correct, msg)
        
        test.terminate()

    # Associated test number within Gradescope
    @number("4")
    # Associated point value within Gradescope
    @weight(5)
    def test_InputSequence12334(self):
        # Title used by Gradescope 
        """Check that input sequence "1, 2, 3, 3, 4" results in correct menu output"""

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/12334.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
            
            # Try to decode stdout
            try:
                stdout = stdout.strip().decode('utf-8')
                stderr = stderr.strip().decode('utf-8')
                test.kill()
                
                # Open reference output and decode
                reference = open('reference/12334.txt', 'rb').read().strip().decode('utf-8')
                
                # Remove empty lines from both output and reference
                stdout = removeEmptyLines(stdout)
                stderr = removeEmptyLines(stderr)
                reference = removeEmptyLines(reference)
                
                # Check if stdout is empty but stderr has content
                if not stdout and stderr:
                    # Check that stderr and reference are equal
                    self.assertEqual(stderr, reference, msg='Program output does not match expected output.')
                # If stderr is not usable for comparison, test with stdout
                else:
                    # Check that stdout and reference are equal
                    self.assertEqual(stdout, reference, msg='Program output does not match expected output.')
            # Catch exception for decode error
            except (UnicodeDecodeError):
                test.kill()
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
                self.longMessage = False
                self.assertTrue(correct, msg)
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
            self.longMessage = False
            self.assertTrue(correct, msg)
        
        test.terminate()
    
    # Associated test number within Gradescope
    @number("5")
    # Associated point value within Gradescope
    @weight(2.5)
    def test_SmallerImageOutputFileCreated(self):
        # Title used by Gradescope 
        """Check that "smallerImage1.pnm" output file is created successfully"""

        # Create a subprocess to run the student's make file to ensure it compiles
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test.kill()
        
        ls = subprocess.Popen(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        lsOut = ls.stdout.read().strip().decode('utf-8').split()
        
        correct = True
        if "smallerImage1.pnm" not in lsOut:
            correct = False

        # Standard unit test case with an associated error message
        self.longMessage = False
        self.assertTrue(correct, msg="Output file \"smallerImage1.pnm\" not created by program.")
        test.terminate()
        
    # Associated test number within Gradescope
    @number("6")
    # Associated point value within Gradescope
    @weight(0.625)
    def test_SmallerImageHeader(self):
        # Title used by Gradescope 
        """Check that "smallerImage1.pnm" header information is correct"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
            
            try:
                header = open('smallerImage1.pnm', 'rb').read().strip().decode('utf-8').split()
                test.kill()

                # Set msg to blank string, in case test passes
                msg = ''
                
                # Array of expected lines of output, line by line in expected order
                expected = ['P3', '640', '360', '255']
                
                # Check if header information is correct
                try:
                    if header[0] != expected[0]:
                        correct = False
                        msg = f'Your PPM image\'s header label is incorrect. Check the Lab 6 instructions for the correct file format label. Your header laber: {header[0]}, expected header label: {expected[0]}'
                    elif header[1] != expected[1]:
                        correct = False
                        msg = f'Your PPM image\'s width is incorrect. Make sure your program uses the width entered by the user for your PPM image. Your width: {header[1]}, expected width: {expected[1]}'
                    elif header[2] != expected[2]:
                        correct = False
                        msg = f'Your PPM image\'s height is incorrect. Check the Lab 6 instructions for where to find the correct height. Your height: {header[2]}, expected height: {expected[2]}'
                    elif header[3] != expected[3]:
                        correct = False
                        msg = f'Your PPM image\'s maximum pixel value is incorrect. Check the Lab 6 instructions for the maximum pixel value of a PPM image. Your value: {header[3]}, expected value: {expected[3]}'
                except (IndexError):
                    correct = False
                    msg = 'Your PPM image\'s header is too short, and cannot be used in autograder comparisons. Ensure your program prints the correct header information.'
            except (OSError):
                correct = False
                msg = '\"smallerImage1.pnm\" could not be opened for comparison.'
            except (UnicodeDecodeError):
                correct = False
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
        
        # Pass test pass/fail status and message to test run output
        self.longMessage = False
        self.assertTrue(correct, msg)
        test.terminate()
        
    # Associated test number within Gradescope
    @number("7")
    # Associated point value within Gradescope
    @weight(0.625)
    def test_SmallerImageFirstPixel(self):
        # Title used by Gradescope 
        """Check that "smallerImage1.pnm" first pixel is correct"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
        
            try:
                output = open('smallerImage1.pnm', 'rb').read().strip().decode('utf-8')
                test.kill()

                # Set msg to blank string, in case test passes
                msg = ''
                
                # Array of expected lines of output, line by line in expected order
                expected = [('203', '226', '242')]
                
                try:
                    # Split program output from header information to pixels
                    encoding, height, width, max, *values = output.split()
                    # Create array of PPM pixels
                    rgb = [tuple(values[i:i+3]) for i in range(0, len(values), 3)]
                    
                    try:
                        # Check that first pixel in image is correct
                        if rgb[0] != expected[0]:
                            correct = False
                            msg = f'The first pixel in your PPM image is incorrect. Your pixel: {rgb[0]}, expected pixel: {expected[0]}'
                    except (IndexError):
                        correct = False
                        msg = 'Your PPM image doesn\'t contain any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.'
                except (ValueError):
                    correct = False
                    msg = 'Your PPM image doesn\'t contain any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.'
            except (OSError):
                correct = False
                msg = '\"smallerImage1.pnm\" could not be opened for comparison.'
            except (UnicodeDecodeError):
                correct = False
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
        
        # Pass test pass/fail status and message to test run output
        self.longMessage = False
        self.assertTrue(correct, msg)
        test.terminate()
        
    # Associated test number within Gradescope
    @number("8")
    # Associated point value within Gradescope
    @weight(0.625)
    def test_SmallerImageLastPixel(self):
        # Title used by Gradescope 
        """Check that "smallerImage1.pnm" last pixel is correct"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
            
            try:
                output = open('smallerImage1.pnm', 'rb').read().strip().decode('utf-8')
                test.kill()

                # Set msg to blank string, in case test passes
                msg = ''
                
                # Array of expected lines of output, line by line in expected order
                expected1 = [('101', '102', '94')]
                expected2 = [('202', '205', '188')]
                expected3 = [('134', '157', '163')]
                expected4 = [('116', '144', '148')]
                expected5 = [('76', '85', '88')]
                expected6 = [('82', '107', '114')]
                expected7 = [('144', '165', '170')]
                expected8 = [('119', '144', '151')]
                expected9 = [('127', '147', '152')]
                expected10 = [('100', '101', '93')]
                
                try:
                    # Split program output from header information to pixels
                    encoding, height, width, max, *values = output.split()
                    # Create array of PPM pixels
                    rgb = [tuple(values[i:i+3]) for i in range(0, len(values), 3)]
                
                    try:
                        # Check that last pixel in image is correct
                        if rgb[len(rgb) - 1] != expected1[0] and rgb[len(rgb) - 1] != expected2[0] and rgb[len(rgb) - 1] != expected3[0] and rgb[len(rgb) - 1] != expected4[0] and rgb[len(rgb) - 1] != expected5[0] and rgb[len(rgb) - 1] != expected6[0] and rgb[len(rgb) - 1] != expected7[0] and rgb[len(rgb) - 1] != expected8[0] and rgb[len(rgb) - 1] != expected9[0] and rgb[len(rgb) - 1] != expected10[0]:
                            correct = False
                            msg = f'The last pixel in your PPM image is incorrect. Your pixel: {rgb[len(rgb) - 1]}, accepted expected pixels: {expected1[0]}, {expected2[0]}, {expected3[0]}, {expected4[0]}, {expected5[0]}, {expected6[0]}, {expected7[0]}, {expected8[0]}, {expected9[0]}, {expected10[0]}'
                    except (IndexError):
                        correct = False
                        msg = 'Your PPM image doesn\'t contain any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.'
                except (ValueError):
                    correct = False
                    msg = 'Your PPM image doesn\'t contain any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.'
            except (OSError):
                correct = False
                msg = '\"smallerImage1.pnm\" could not be opened for comparison.'
            except (UnicodeDecodeError):
                correct = False
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
        
        # Pass test pass/fail status and message to test run output
        self.longMessage = False
        self.assertTrue(correct, msg)
        test.terminate()
        
    # Associated test number within Gradescope
    @number("9")
    # Associated point value within Gradescope
    @weight(0.625)
    def test_SmallerImageNumPixels(self):
        # Title used by Gradescope 
        """Check that "smallerImage1.pnm" has correct number of pixels"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
            
            try:
                output = open('smallerImage1.pnm', 'rb').read().strip().decode('utf-8')
                test.kill()

                # Set msg to blank string, in case test passes
                msg = ''
                
                # Check that length of rgb array is correct, if not then fail test and set appropriate error
                try:
                    # Split program output from header information to pixels
                    encoding, height, width, max, *values = output.split()
                    # Create array of PPM pixels
                    rgb = [tuple(values[i:i+3]) for i in range(0, len(values), 3)]
                    
                    if len(rgb) < 230400:
                        correct = False
                        msg = f'Your PPM image contains fewer pixels than expected. Your length: {len(rgb)} pixels, expected length: 230,400 pixels'
                    elif len(rgb) > 230400:
                        correct = False
                        msg = f'Your PPM image contains more pixels than expected. Your length: {len(rgb)} pixels, expected length: 230,400 pixels'
                except (ValueError):
                    correct = False
                    msg = 'Your PPM image doesn\'t contain any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.'
            except (OSError):
                correct = False
                msg = '\"smallerImage1.pnm\" could not be opened for comparison.'
            except (UnicodeDecodeError):
                correct = False
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
        
        # Pass test pass/fail status and message to test run output
        self.longMessage = False
        self.assertTrue(correct, msg)
        test.terminate()
        
    # Associated test number within Gradescope
    @number("10")
    # Associated point value within Gradescope
    @weight(2.5)
    def test_GreenScreenOutputFileCreated(self):
        # Title used by Gradescope 
        """Check that "greenScreen1.pnm" output file is created successfully"""

        # Create a subprocess to run the student's make file to ensure it compiles
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test.kill()
        
        ls = subprocess.Popen(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        lsOut = ls.stdout.read().strip().decode('utf-8').split()
        
        correct = True
        if "greenScreen1.pnm" not in lsOut:
            correct = False

        # Standard unit test case with an associated error message
        self.longMessage = False
        self.assertTrue(correct, msg="Output file \"greenScreen1.pnm\" not created by program.")
        test.terminate()
        
    # Associated test number within Gradescope
    @number("11")
    # Associated point value within Gradescope
    @weight(2.5)
    def test_GreenScreenImageComparison(self):
        # Title used by Gradescope 
        """Check that "greenScreen1.pnm" image contents are correct"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
            
            # Try to decode stdout
            try:
                output = open('greenScreen1.pnm', 'rb').read().strip().decode('utf-8')
                test.kill()
                
                # Open reference output and decode
                reference = open('reference/greenScreen1.pnm', 'rb').read().strip().decode('utf-8')
                
                # Check that output and reference are equal
                self.assertEqual(output, reference, msg='Copied PNM image is not identical to sample.')
            except (OSError):
                correct = False
                msg = '\"greenScreen1.pnm\" could not be opened for comparison.'
                self.longMessage = False
                self.assertTrue(correct, msg)
            except (UnicodeDecodeError):
                test.kill()
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
                self.longMessage = False
                self.assertTrue(correct, msg)
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
            self.longMessage = False
            self.assertTrue(correct, msg)
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
            self.longMessage = False
            self.assertTrue(correct, msg)
        
        test.terminate()
        
    # Associated test number within Gradescope
    @number("12")
    # Associated point value within Gradescope
    @weight(10)
    def test_ReducedGreenScreenOutputFileCreated(self):
        # Title used by Gradescope 
        """Check that "smallerImage2.pnm" output file is created successfully"""

        # Create a subprocess to run the student's make file to ensure it compiles
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test.kill()
        
        ls = subprocess.Popen(["ls"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        lsOut = ls.stdout.read().strip().decode('utf-8').split()
        
        correct = True
        if "smallerImage2.pnm" not in lsOut:
            correct = False

        # Standard unit test case with an associated error message
        self.longMessage = False
        self.assertTrue(correct, msg="Output file \"smallerImage2.pnm\" not created by program.")
        test.terminate()
        
    # Associated test number within Gradescope
    @number("13")
    # Associated point value within Gradescope
    @weight(2.5)
    def test_ReducedGreenScreenImageHeader(self):
        # Title used by Gradescope 
        """Check that "smallerImage2.pnm" header information is correct"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
            
            try:
                header = open('smallerImage2.pnm', 'rb').read().strip().decode('utf-8').split()
                test.kill()

                # Set msg to blank string, in case test passes
                msg = ''
                
                # Array of expected lines of output, line by line in expected order
                expected = ['P3', '640', '360', '255']
                
                # Check if header information is correct
                try:
                    if header[0] != expected[0]:
                        correct = False
                        msg = f'Your PPM image\'s header label is incorrect. Check the Lab 6 instructions for the correct file format label. Your header laber: {header[0]}, expected header label: {expected[0]}'
                    elif header[1] != expected[1]:
                        correct = False
                        msg = f'Your PPM image\'s width is incorrect. Make sure your program uses the width entered by the user for your PPM image. Your width: {header[1]}, expected width: {expected[1]}'
                    elif header[2] != expected[2]:
                        correct = False
                        msg = f'Your PPM image\'s height is incorrect. Check the Lab 6 instructions for where to find the correct height. Your height: {header[2]}, expected height: {expected[2]}'
                    elif header[3] != expected[3]:
                        correct = False
                        msg = f'Your PPM image\'s maximum pixel value is incorrect. Check the Lab 6 instructions for the maximum pixel value of a PPM image. Your value: {header[3]}, expected value: {expected[3]}'
                except (IndexError):
                    correct = False
                    msg = 'Your PPM image\'s header is too short, and cannot be used in autograder comparisons. Ensure your program prints the correct header information.'
            except (OSError):
                correct = False
                msg = '\"smallerImage2.pnm\" could not be opened for comparison.'
            except (UnicodeDecodeError):
                correct = False
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
        
        # Pass test pass/fail status and message to test run output
        self.longMessage = False
        self.assertTrue(correct, msg)
        test.terminate()
        
    # Associated test number within Gradescope
    @number("14")
    # Associated point value within Gradescope
    @weight(2.5)
    def test_ReducedGreenScreenImageNumPixels(self):
        # Title used by Gradescope 
        """Check that "smallerImage2.pnm" has correct number of pixels"""
        
        # Boolean to hold value for test pass/fail (updated procedurally)
        correct = True

        # Create a subprocess to run the students code to obtain an output
        cat = subprocess.Popen(["cat", "input/1234.txt"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        test = subprocess.Popen(["make -s run"], shell=True, stdin=cat.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = test.communicate()
        
        try:
            checkRuntimeErrors(test)
            
            try:
                output = open('smallerImage2.pnm', 'rb').read().strip().decode('utf-8')
                test.kill()

                # Set msg to blank string, in case test passes
                msg = ''
                
                # Check that length of rgb array is correct, if not then fail test and set appropriate error
                try:
                    # Split program output from header information to pixels
                    encoding, height, width, max, *values = output.split()
                    # Create array of PPM pixels
                    rgb = [tuple(values[i:i+3]) for i in range(0, len(values), 3)]
                    
                    if len(rgb) < 230400:
                        correct = False
                        msg = f'Your PPM image contains fewer pixels than expected. Your length: {len(rgb)} pixels, expected length: 230,400 pixels'
                    elif len(rgb) > 230400:
                        correct = False
                        msg = f'Your PPM image contains more pixels than expected. Your length: {len(rgb)} pixels, expected length: 230,400 pixels'
                except (ValueError):
                    correct = False
                    msg = 'Your PPM image doesn\'t contain any pixels, and can\'t be used in comparisons. Ensure your program prints at least one pixel.'
            except (OSError):
                correct = False
                msg = '\"smallerImage2.pnm\" could not be opened for comparison.'
            except (UnicodeDecodeError):
                correct = False
                msg = 'Your program printed a character that the autograder cannot decode. Ensure your program prints valid characters.'
        except (RuntimeAbort):
            correct = False
            msg = 'Your program triggered runtime error SIGABRT. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeSegFault):
            correct = False
            msg = 'Your program encountered a segmentation fault. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeFPE):
            correct = False
            msg = 'Your program triggered runtime error SIGFPE (typically caused by dividing by zero). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeBusError):
            correct = False
            msg = 'Your program triggered runtime error SIGBUS. Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (RuntimeIllegalInstruction):
            correct = False
            msg = 'Your program triggered runtime error SIGILL (typically caused by stack smashing). Check for compilation warnings, use GDB to track down the cause of this error, or Google this error for more information.'
        except (MakefileError):
            correct = False
            msg = (stderr.strip().decode('utf-8') + '\n' + stdout.strip().decode('utf-8')).split('\n')
        
        # Pass test pass/fail status and message to test run output
        self.longMessage = False
        self.assertTrue(correct, msg)
        test.terminate()