/*
 * TestUnitTestSuite.java
 * JUnit based test
 *
 * Created on January 15, 2007, 4:06 PM
 */

package org.jruby.test;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.exceptions.RaiseException;

/**
 *
 * @author headius
 */
public class TestUnitTestSuite extends TestCase {
    private static final String TEST_DIR = "test";
    private static final String TEST_INDEX = "test_unit_index";
    private static final String REGEXP_TEST_CASE_SUBCLASS = "^\\s*class\\s+([^\\s]+)\\s*<.*TestCase\\s*$";
    private static final Pattern PATTERN_TEST_CASE_SUBCLASS = Pattern.compile(REGEXP_TEST_CASE_SUBCLASS);

    public TestUnitTestSuite(String testName) {
        super(testName);
    }

    /**
     * suite method automatically generated by JUnit module
     */
    public static Test suite() throws Exception {
        TestSuite suite = new TestSuite();

        File testDir;
        if (System.getProperty("basedir") != null) {
            testDir = new File(System.getProperty("basedir"), "target/test-classes/" + TEST_DIR);
        } else {
            testDir = new File(TEST_DIR);
        }

        File testIndex = new File(testDir, TEST_INDEX);

        if (!testIndex.canRead()) {
            // Since we don't have any other error reporting mechanism, we
            // add the error message as an always-failing test to the test suite.
            suite.addTest(new FailingTest("TestUnitTestSuite",
                                          "Couldn't locate " + TEST_INDEX +
                                          ". Make sure you run the tests from the base " +
                                          "directory of the JRuby sourcecode."));
            return suite;
        }

        BufferedReader testFiles =
            new BufferedReader(new InputStreamReader(new FileInputStream(testIndex)));

        String line;
        while ((line = testFiles.readLine()) != null) {
            line = line.trim();
            if (line.startsWith("#") || line.length() == 0) {
                continue;
            }

            suite.addTest(new ScriptTest(line, testDir));
        }

        return suite;
    }

    private static class ScriptTest extends TestCase {
        private ByteArrayInputStream in;
        private ByteArrayOutputStream out;
        private PrintStream printOut;
        private ByteArrayOutputStream err;
        private PrintStream printErr;
        private Ruby runtime;
        private final String filename;
        private final File testDir;

        public ScriptTest(String filename, File dir) {
            super(filename);
            this.filename = filename;
            this.testDir = dir;
        }

        protected void setUp() throws Exception {
            in = new ByteArrayInputStream(new byte[0]);
            out = new ByteArrayOutputStream();
            err = new ByteArrayOutputStream();
            runtime = Ruby.newInstance(in, printOut = new PrintStream(out), printErr = new PrintStream(err));
            setupInterpreter(runtime);
        }

        protected void tearDown() throws Exception {
            in.close();
            out.close();
            err.close();
            printOut.close();
            printErr.close();

            in = null;
            out = null;
            err = null;
            printOut = null;
            printErr = null;
            runtime = null;
        }

        private void setupInterpreter(Ruby runtime) {
            ArrayList loadPath = new ArrayList();
            
            loadPath.add("test/externals/bfts");
            loadPath.add("test/externals/ruby_test/lib");
            
            runtime.getLoadService().init(loadPath);
            runtime.defineGlobalConstant("ARGV", runtime.newArray());
        }

        private String scriptName() {
            return new File(testDir, filename).getPath();
        }

        private String pretty(List list) {
            StringBuffer prettyOut = new StringBuffer();

            for (Iterator iter = list.iterator(); iter.hasNext();) {
                prettyOut.append(iter.next().toString());
            }

            return prettyOut.toString();
        }
        
        private String getTestClassNameFromReadingTestScript(String filename) {
            File f = new File(TEST_DIR + "/" + filename + ".rb");
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader(f));
                while (true) {
                    String line = reader.readLine();
                    if (line == null) break;
                    Matcher m = PATTERN_TEST_CASE_SUBCLASS.matcher(line);
                    if (m.find())
                    {
                        return m.group(1);
                    }
                }
                    
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        throw new RuntimeException("Could not close reader!", e);
                    }
                }
                
            }
            throw new RuntimeException("No *TestCase derivative found in '" + filename + ".rb'!");
        }
        

        public void runTest() throws Throwable {
            StringBuffer script = new StringBuffer();

            try {
                script.append("require 'test/junit_testrunner.rb'\n");
                script.append("require '" + scriptName() + "'\n");
                script.append("runner = Test::Unit::UI::JUnit::TestRunner.new(" + getTestClassNameFromReadingTestScript(filename) + ")\n");
                script.append("runner.start\n");
                script.append("runner.faults\n");

                RubyArray faults = (RubyArray)runtime.evalScript(script.toString());

                if (!faults.isEmpty()) {
                    StringBuffer faultString = new StringBuffer("Faults encountered running " + scriptName() + ", complete output follows:\n");
                    for (Iterator iter = faults.iterator(); iter.hasNext();) {
                        String fault = iter.next().toString();

                        faultString.append(fault).append("\n");
                    }

                    fail(faultString.toString());
                }
            } catch (RaiseException re) {
                fail("Faults encountered running " + scriptName() + ", complete output follows:\n" + re.getException().message + "\n" + pretty(((RubyArray)re.getException().backtrace()).getList()));
            }
        }
    }
}
