#!/usr/bin/env python3

import posix
import subprocess

# description: human-readable description of test.
# filename: filename test code should assume.
# errors: error messages pedansee should produce for test.
# code: code which test provides to pedansee as input.

TESTS = (
    {
        'description': 'Ignored return value from function',
        'filename':    'xxx.c',
        'errors':   ( b'return value from xxx_foo ignored',
                      b'no unit-test for xxx_foo()',
                      b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

void
xxx_bar(void)
{
    xxx_foo();
}
'''
    },
    {
        'description': 'Return value used in variable declaration',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

void
xxx_bar(void)
{
    int i = xxx_foo();
}
'''
    },
    {
        'description': 'Return value used in return',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

int
xxx_bar(void)
{
    return xxx_foo();
}
'''
    },
    {
        'description': 'Return value used in switch',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

int
xxx_bar(void)
{
    switch (xxx_foo()) {
    default:
        break;
    }
}
'''
    },
    {
        'description': 'Return value used in if',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

int
xxx_bar(void)
{
    if (xxx_foo()) {}
}
'''
    },
    {
        'description': 'Return value used in while',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

int
xxx_bar(void)
{
    while (xxx_foo()) {}
}
'''
    },
    {
        'description': 'Return value used in do',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

int
xxx_bar(void)
{
    do {} while (xxx_foo());
}
'''
    },
    {
        'description': 'Return value used in for',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

int
xxx_bar(void)
{
    for (int i = 0; xxx_foo(); i++) {}
}
'''
    },
    {
        'description': 'Return value used in binary expression',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

int
xxx_bar(void)
{
    0 && xxx_foo();
}
'''
    },
    {
        'description': 'Return value used in arithmetic expression',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

int
xxx_foo(void)
{
    return 0;
}

int
xxx_bar(void)
{
    0 + xxx_foo();
}
'''
    },
    {
        'description': 'No return value from void function',
        'filename':    'xxx.c',
        'errors':       ( b'no unit-test for xxx_foo()',
                          b'no unit-test for xxx_bar()' ),
        'code':        '''
#include <stdio.h>

void
xxx_foo(void)
{
}

int
xxx_bar(void)
{
    xxx_foo();
}
'''
    },
    {
        'description': 'Multiple return statements',
        'filename':    'xxx.c',
        'errors':   ( b'2 return statements in xxx_foo',
                      b'no unit-test for xxx_foo()' ),
        'code':        '''
#include <stdio.h>

void
xxx_foo(void)
{
    if (0) { return; } else { return; }
}
'''
    },
    {
        'description': 'Static global int does not start with \'_\'',
        'filename':    'xxx.c',
        'errors':   ( b'static id foo ill-named', ),
        'code':        '''
#include <stdio.h>

static int foo;
'''
    },
    {
        'description': 'Static global int starts with \'_\' and filename',
        'filename':    'xxx.c',
        'errors':   ( b'static id _xxx_foo contains filename xxx', ),
        'code':        '''
#include <stdio.h>

static int _xxx_foo;
'''
    },
    {
        'description': 'Non-static global int starts with \'_\'',
        'filename':    'xxx.c',
        'errors':   ( b'non-static id _foo ill-named', ),
        'code':        '''
#include <stdio.h>

int _foo;
'''
    },
    {
        'description': 'Non-static global int does not start with filename prefix',
        'filename':    'xxx.c',
        'errors':   ( b'id foo does not start with xxx_', ),
        'code':        '''
#include <stdio.h>

int foo;
'''
    },
    {
        'description': 'Static function does not start with \'_\'',
        'filename':    'xxx.c',
        'errors':   ( b'static id foo ill-named',
                      b'no unit-test for foo()' ),
        'code':        '''
#include <stdio.h>

static void
foo(void) {}
'''
    },
    {
        'description': 'Non-static function starts with \'_\'',
        'filename':    'xxx.c',
        'errors':   ( b'non-static id _foo ill-named',
                      b'no unit-test for _foo()' ),
        'code':        '''
#include <stdio.h>

void
_foo(void) {}
'''
    },
    {
        'description': 'Non-static function does not start with filename prefix',
        'filename':    'xxx.c',
        'errors':   ( b'id foo does not start with xxx_',
                      b'no unit-test for foo()' ),
        'code':        '''
#include <stdio.h>

void
foo(void) {}
'''
    },
    {
        'description': 'Lower case constant identifier',
        'filename':    'xxx.c',
        'errors':   ( b'id foo does not start with xxx_', ),
        'code':        '''
#include <stdio.h>

const int foo;
'''
    },
    {
        'description': 'Function name not on column one',
        'filename':    'xxx.c',
        'errors':   ( b'function xxx_foo name not on column one',
                      b'no unit-test for xxx_foo()' ),
        'code':        '''
#include <stdio.h>

void xxx_foo(void)
{
    int x;
    if (0 == x) { return; }
}
'''
    },
    {
        'description': 'Missing \'{\' following if',
        'filename':    'xxx.c',
        'errors':   ( b'missing \'{\' and \'}\' following if',
                      b'no unit-test for xxx_foo()' ),
        'code':        '''
#include <stdio.h>

void
xxx_foo(void)
{
    int x;
    if (0 == x) printf("Hello, world!");
}
'''
    },
    {
        'description': 'Missing \'{\' following else',
        'filename':    'xxx.c',
        'errors':   ( b'missing \'{\' and \'}\' following else',
                      b'no unit-test for xxx_foo()' ),
        'code':        '''
#include <stdio.h>

void
xxx_foo(void)
{
    int x;
    if (0 == x) {} else printf("Hello, world!");
}
'''
    },
    {
        'description': 'Missing \'{\' following while',
        'filename':    'xxx.c',
        'errors':   ( b'missing \'{\' and \'}\' following while',
                      b'no unit-test for xxx_foo()' ),
        'code':        '''
#include <stdio.h>

void
xxx_foo(void)
{
    int x;
    while (0 == x) printf("Hello, world!");
}
'''
    },
    {
        'description': 'Missing \'{\' following do',
        'filename':    'xxx.c',
        'errors':   ( b'missing \'{\' and \'}\' following do',
                      b'no unit-test for xxx_foo()' ),
        'code':        '''
#include <stdio.h>

void
xxx_foo(void)
{
    int x;
    do return; while (0);
}
'''
    },
    {
        'description': 'Missing \'{\' following for',
        'filename':    'xxx.c',
        'errors':   ( b'missing \'{\' and \'}\' following for',
                      b'no unit-test for xxx_foo()' ),
        'code':        '''
#include <stdio.h>

void
xxx_foo(void)
{
    for (int i = 0; i < 10; i++) return;
}
'''
    },
    {
        'description': 'Missing guard in header',
        'filename':    'xxx.h',
        'errors':   ( b'missing guard #ifndef _XXX_H',
                      b'missing guard #define _XXX_H', ),
        'code':        '''
'''
    },
    {
        'description': 'Misnamed guard in header',
        'filename':    'xxx.h',
        'errors':   ( b'missing guard #ifndef _XXX_H',
                      b'missing guard #define _XXX_H', ),
        'code':        '''
#ifndef XXX_H
#define XXX_H
#endif
'''
    },
    {
        'description': 'Correct guard in header',
        'filename':    'xxx.h',
        'errors':       (),
        'code':        '''
#ifndef _XXX_H
#define _XXX_H
#endif
'''
    },
)

for test in TESTS:
    fail = None
    errcount = 0

    tmp = open(test['filename'], 'w')
    print(test['code'], file=tmp)
    tmp.close()

    popen = subprocess.Popen(('./pedansee', '-c', 'pedansee.conf', test['filename']),
                              stdin=subprocess.PIPE,
                              stderr=subprocess.PIPE)
    output = popen.communicate()[1]

    posix.unlink(test['filename'])

    if type(test['errors']) != type(()):
        print('*** ERROR: errors associated with "', test['description'], '" not a tuple', sep='')
        continue

    if () == test['errors']:
        if b'' != output:
            fail = output
    else:
        for needle in test['errors']:
            if not needle in output:
                fail = needle
                break
            else:
                errcount += 1

    if errcount != len(output.split(b'\n')) - 1:
        fail = output

    if fail != None:
        print('*** FAIL:  ', end='')
    else:
        print('    PASS:  ', end='')

    print(test['description'], end='')

    if fail != None:
        if () == test['errors']:
            print('; extra error(s):', fail)
        else:
            print('; absent error:', fail)
    else:
        print()
