/*
 * LparseWrapperTest.java
 * JUnit based test
 *
 * Copyright (C) 2006 - 2007 Martin Slota
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * History:
 * v0.1 (2007-01-08): initial version
 * v0.2 (2007-01-27):
 * - tests finally written
 * - documentation added
 * v0.2.1 (2007-01-28):
 * - unnecessary throws clauses removed
 * v0.2.2 (2007-02-08):
 * - &8212; fixed (to &#8212;) in javadocs
 * v0.2.3 (2007-02-14):
 * - tests from LparseOutputFilterTest and LparseTest moved here
 * v0.2.4 (2007-03-06):
 * - testSetLparsePath added
 * v1.0.0 (2007-05-05):
 * - tests adapted to suit the new version of LparseWrapper
 */

package lp.wrap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import junit.framework.*;

/**
 * Contains tests of the {@link LparseWrapper} class.
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LparseWrapper
 */
public class LparseWrapperTest extends TestCase {
	/**
	 * The {@link LparseWrapper} instance used in the tests.
	 */
	private final LparseWrapper lw = new LparseWrapper();
	
	/**
	 * A default test case constructor.
	 *
	 * @param testName the name of the test case
	 */
	public LparseWrapperTest(String testName) {
		super(testName);
	}
	
	/**
	 * Tests the {@link LparseWrapper#setLparsePath(String)} method.
	 */
	public void testSetLparsePath() throws IOException {
		try {
			lw.setLparsePath(null);
			fail("An IllegalArgumentException expected!");
		} catch (IllegalArgumentException e) {
			// as expected
		}
		
		try {
			lw.setLparsePath("");
			fail("An IllegalArgumentException expected!");
		} catch (IllegalArgumentException e) {
			// as expected
		}
		
		lw.setLparsePath("some_nonsense");
		try {
			lw.exec();
			fail("A WrapperException expected!");
		} catch (WrapperException e) {
			assertEquals("The error message not as expected!",
					"some_nonsense could not be executed (path " +
					"`some_nonsense' is probably invalid)", e.getMessage());
		}
	}
	
	/**
	 * Tests the {@link LparseWrapper#setOptions(String)} and
	 * {@link LparseWrapper#exec()} methods&#8212;sets a simple option string
	 * and a simple input string and tests if the correct output is produced by
	 * lparse.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 *
	 * @throws WrapperException if an {@link IOException} occurs while creating
	 * the process or if an {@link InterruptedException} occurs while waiting
	 * for the process instance to finish execution.
	 * @throws IOException if an I/O error occurs while setting the standard
	 * input or while reading the output of the process (should never happen)
	 */
	public void testSetOptions() throws IOException {
		lw.setOptions("-t");
		lw.exec();
		lw.closeStdin();
		lw.waitFor();
		
		List<String> expected = new ArrayList<String>();
		expected.add("compute 1 { not _false }.");
		
		BufferedReader outputReader = new BufferedReader(
				new InputStreamReader(lw.getStdout()));
		
		for (String line : expected) {
			assertEquals("Lparse output not as expected!",
					line, outputReader.readLine().trim());
		}
		assertNull("Lparse output not as expected!",
				outputReader.readLine());
	}
	
	/**
	 * Tests the {@link LparseWrapper#exec()} method&#8212;sets a simple input
	 * string and tests if the correct output is produced by lparse.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 *
	 * @throws WrapperException if an {@link IOException} occurs while creating
	 * the process or if an {@link InterruptedException} occurs while waiting
	 * for the process instance to finish execution.
	 * @throws IOException if an I/O error occurs while setting its standard
	 * input or while reading the output of the process (should never happen)
	 */
	public void testSimpleInput() throws IOException {
		lw.setOptions(null);
		lw.exec();
		
		WrapperUtils.getInstance().transfer("a. b. c.", lw.getStdin());
		lw.closeStdin();
		
		lw.waitFor();
		
		List<String> expected = new ArrayList<String>();
		expected.add("1 2 0 0");
		expected.add("1 3 0 0");
		expected.add("1 4 0 0");
		expected.add("0");
		expected.add("2 c");
		expected.add("3 b");
		expected.add("4 a");
		expected.add("0");
		expected.add("B+");
		expected.add("0");
		expected.add("B-");
		expected.add("1");
		expected.add("0");
		expected.add("1");
		
		BufferedReader outputReader = new BufferedReader(
				new InputStreamReader(lw.getStdout()));
		
		for (String line : expected) {
			assertEquals("Lparse output not as expected!",
					line, outputReader.readLine().trim());
		}
		assertNull("Lparse output not as expected!",
				outputReader.readLine());
	}
	
	/**
	 * Tests {@link LparseWrapper} on an error free input.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 *
	 * @throws IOException if an I/O error occurs while testing (should never
	 * happen)
	 * @throws WrapperException if an error occurs while executing lparse
	 * (should never happen)
	 */
	public void testErrorFreeInput() throws IOException {
		// prepare the input string
		String input = "d(1..100).\n" +
				"a(X) :- d(X), not b(X).\n" +
				"b(X) :- d(X), not a(X).";
		
		// prepare expected output
		Set<String> expOutput = new HashSet<String>();
		for (int i = 1; i <= 100; i++) {
			expOutput.add("d(" + i + ").");
			expOutput.add("a(" + i + ") :- not b(" + i + ").");
			expOutput.add("b(" + i + ") :- not a(" + i + ").");
		}
		expOutput.add("compute 1 { not _false }.");
		
		// prepare expected exceptions
		List<LparseMessage> expWarnings = new ArrayList<LparseMessage>();
		LparseMessage expError = null;
		
		doTest(input, expOutput, expError, expWarnings);
	}
	
	/**
	 * Tests {@link LparseWrapper} on an input with a single error and no
	 * warnings.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 *
	 * @throws IOException if an I/O error occurs while testing (should never
	 * happen)
	 * @throws WrapperException if an error occurs while executing lparse
	 * (should never happen)
	 */
	public void testInputWithError() throws IOException {
		// prepare the input string
		String input = "a(X).";
		
		// prepare expected output
		Set<String> expOutput = new HashSet<String>();
		
		// prepare expected exceptions
		List<LparseMessage> expWarnings = new ArrayList<LparseMessage>();
		LparseMessage expError = new LparseMessage(
				"A non-ground fact: a(X). All terms within a fact must be ground terms.", 1);
		
		doTest(input, "-W error -t", expOutput, expError, expWarnings);
	}
	
	/**
	 * Tests {@link LparseWrapper} on an input with a single error but without
	 * an error message. This situation sometimes occurs when lparse considers
	 * warnings as errors.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 *
	 * @throws IOException if an I/O error occurs while testing (should never
	 * happen)
	 * @throws WrapperException if an error occurs while executing lparse
	 * (should never happen)
	 */
	public void testWarningAsError() throws IOException {
		// prepare the input string
		String input = "a(X) :- d(X), b(x).";
		
		// prepare expected output
		Set<String> expOutput = new HashSet<String>();
		
		// prepare expected exceptions
		List<LparseMessage> expWarnings = new ArrayList<LparseMessage>();
		LparseMessage expError = new LparseMessage();
		
		doTest(input, "-W error -t", expOutput, expError, expWarnings);
	}
	
	/**
	 * Tests {@link LparseWrapper} on an input with a single error with an error
	 * message that begins with "Error:". This situation sometimes occurs when
	 * lparse considers warnings as errors.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 *
	 * @throws IOException if an I/O error occurs while testing (should never
	 * happen)
	 * @throws WrapperException if an error occurs while executing lparse
	 * (should never happen)
	 */
	public void testWarningAsError2() throws IOException {
		// prepare the input string
		String input = "a :- b.";
		
		// prepare expected output
		Set<String> expOutput = new HashSet<String>();
		
		// prepare expected exceptions
		List<LparseMessage> expWarnings = new ArrayList<LparseMessage>();
		LparseMessage expError = new LparseMessage(
				"Predicate 'b/0' doesn't occur in any rule head.", 1);
		
		doTest(input, "-W error -t", expOutput, expError, expWarnings);
	}
	
	/**
	 * Tests {@link LparseWrapper} on an input with a single warning and no
	 * error.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 *
	 * @throws IOException if an I/O error occurs while testing (should never
	 * happen)
	 * @throws WrapperException if an error occurs while executing lparse
	 * (should never happen)
	 */
	public void testOneWarning() throws IOException {
		// prepare the input string
		String input = "a :- b.";
		
		// prepare expected output
		Set<String> expOutput = new HashSet<String>();
		expOutput.add("compute 1 { not _false }.");
		
		// prepare expected exceptions
		List<LparseMessage> expWarnings = new ArrayList<LparseMessage>();
		expWarnings.add(new LparseMessage(
				"Predicate 'b/0' doesn't occur in any rule head.", 1, true));
		LparseMessage expError = null;
		
		doTest(input, expOutput, expError, expWarnings);
	}
	
	/**
	 * Tests {@link LparseWrapper} on an input with a multiple warnings and
	 * no error.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 *
	 * @throws IOException if an I/O error occurs while testing (should never
	 * happen)
	 * @throws WrapperException if an error occurs while executing lparse
	 * (should never happen)
	 */
	public void testMultipleWarnings() throws IOException {
		// prepare the input string
		String input = "\na(X) :- d(X), b(x).";
		
		// prepare expected output
		Set<String> expOutput = new HashSet<String>();
		expOutput.add("compute 1 { not _false }.");
		
		// prepare expected exceptions
		List<LparseMessage> expWarnings = new ArrayList<LparseMessage>();
		expWarnings.add(new LparseMessage("Predicate 'd/1' doesn't occur in any rule head.", 2, true));
		expWarnings.add(new LparseMessage("Predicate 'b/1' doesn't occur in any rule head.", 2, true));
		expWarnings.add(new LparseMessage("Variable 'X' is similar to constant 'x' " +
				"(other occurrences of 'X' are not checked)", 2, true));
		expWarnings.add(new LparseMessage("Constant 'x' is similar to variable 'X' " +
				"(other occurrences of 'x' are not checked)", 2, true));
		LparseMessage expError = null;
		
		doTest(input, expOutput, expError, expWarnings);
	}
	
	/**
	 * Has the same effect as calling
	 * {@code doTest(input, "-t", expOutput, expError, expWarnings)}.
	 *
	 * @param input the lparse input to test
	 * @param expOutput the expected lparse output
	 * @param expError the expected lparse error
	 * @param expWarnings the expected lparse warnings
	 * @throws IOException if an I/O error occurs while testing (should never
	 * happen)
	 * @throws WrapperException if an error occurs while executing lparse
	 * (should never happen)
	 */
	private void doTest(String input, Set<String> expOutput,
			LparseMessage expError, List<LparseMessage> expWarnings)
			throws IOException, WrapperException {
		doTest(input, "-t", expOutput, expError, expWarnings);
	}
	
	/**
	 * Performs a single call of
	 *<pre>
	 *lw.setInput(input);
	 *lw.setOptions(options);
	 *InputStream outputStream = lw.exec();
	 *</pre>
	 *
	 * and tests if expected output, error and warnings are detected by
	 * {@code lw}.
	 *
	 * @param input the lparse input to test
	 * @param options the lparse options to test
	 * @param expOutput the expected lparse output
	 * @param expError the expected lparse error
	 * @param expWarnings the expected lparse warnings
	 * @throws IOException if an I/O error occurs while testing (should never
	 * happen)
	 * @throws WrapperException if an error occurs while executing lparse
	 * (should never happen)
	 */
	private void doTest(String input, String options, Set<String> expOutput,
			LparseMessage expError, List<LparseMessage> expWarnings)
			throws IOException {
		// execute lparse
		lw.setOptions(options);
		lw.exec();
		
		WrapperUtils.getInstance().transfer(input, lw.getStdin());
		lw.closeStdin();
		
		// store output as a set of lines
		Set<String> output = new HashSet<String>();
		BufferedReader outputReader = new BufferedReader(
				new InputStreamReader(lw.getStdout()));
		String line = outputReader.readLine();
		while (line != null) {
			if (!line.trim().equals(""))
				output.add(line);
			line = outputReader.readLine();
		}
		
		lw.waitFor();
		
		// check the output, error and warnings
		assertEquals("Lparse output not as expected", expOutput, output);
		assertEquals("Lparse error not as expected!", expError, lw.getError());
		assertEquals("Lparse warnings not as expected!",
				expWarnings, lw.getWarnings());
	}
}