diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6b8dde0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: java +jdk: + - oraclejdk8 +os: + - linux +script: + - chmod +x buildscript.sh && ./buildscript.sh diff --git a/05.XUnit/pom.xml b/05.XUnit/pom.xml new file mode 100644 index 0000000..a08fa59 --- /dev/null +++ b/05.XUnit/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + XUnit + XUnit + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + junit + junit + 4.12 + + + + + \ No newline at end of file diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/Main.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/Main.java new file mode 100644 index 0000000..550b056 --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/Main.java @@ -0,0 +1,54 @@ +package ru.spbau.mit.alyokhina; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; + + +/** + * Class for print tests results + */ +public class Main { + /** + * print result + * @param args class name + */ + public static void main(String[] args) { + if (args.length != 1) { + System.out.println("Expected class name"); + return; + } + try { + TestExecutor testExecutor = new TestExecutor(Class.forName(args[0])); + + List results = testExecutor.run(); + int all = 0, success = 0, fail = 0, ignored = 0; + for (TestResult testResult : results) { + System.out.print("Class: " + testResult.getClassName() + ", Test: " + testResult.getTestName() + ", Time: " + ((Long) testResult.getTime()).toString()); + all++; + if (testResult.isFail()) { + fail++; + System.out.print(" -- Failed: "); + if (testResult.getException() != null) { + System.out.println(testResult.getException().getMessage()); + } else { + System.out.println("Expected exception not received"); + } + } else { + if (testResult.causeOfIgnoring().equals("")) { + success++; + System.out.println(" -- Success"); + } else { + ignored++; + System.out.println(" -- Ignored: " + testResult.causeOfIgnoring()); + } + } + System.out.println("All: " + ((Integer) all).toString() + ", success: " + ((Integer) success).toString() + + ", fail: " + ((Integer) fail).toString() + ", ignored: " + ((Integer) ignored).toString()); + } + } catch (InstantiationException | IllegalAccessException | TestExecutorException | + InvocationTargetException | ClassNotFoundException e) { + System.out.println(e.getMessage()); + } + } + +} diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestExecutor.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestExecutor.java new file mode 100644 index 0000000..d5a6916 --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestExecutor.java @@ -0,0 +1,195 @@ +package ru.spbau.mit.alyokhina; + +import ru.spbau.mit.alyokhina.annotation.*; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** Class for tests run */ +public class TestExecutor { + /** Instance of class for calling tests */ + private Object instance; + + /** Class name for test runs */ + private String testClassName; + + /** Methods with annotation Before */ + private List before = new ArrayList<>(); + + /** Methods with annotation BeforeClass */ + private List beforeClass = new ArrayList<>(); + + /** Methods with annotation After */ + private List after = new ArrayList<>(); + + /** Methods with annotation AfterClass */ + private List afterClass = new ArrayList<>(); + + /** Methods with annotation Test */ + private List tests = new ArrayList<>(); + + + /** + * Constructor + * @param clazz class from which tests will be run + * @throws IllegalAccessException if newInstance threw IllegalAccessException + * @throws InstantiationException if newInstance threw InstantiationException + * @throws TestExecutorException if if wrong number methods with annotation + */ + public TestExecutor(Class clazz) throws IllegalAccessException, InstantiationException, TestExecutorException { + getMethods(clazz); + instance = clazz.newInstance(); + testClassName = clazz.getName(); + } + + + /** + * Run tests + * @return list of results of each test + * @throws InvocationTargetException if we catch exception from the BeforeClass or the AfterClass + * @throws IllegalAccessException if invoke threw InvocationTargetException + */ + public List run() throws InvocationTargetException, IllegalAccessException { + List results = new ArrayList<>(); + if (beforeClass.size() != 0) { + beforeClass.get(0).setAccessible(true); + beforeClass.get(0).invoke(instance); + } + + for (Method method : tests) { + method.setAccessible(true); + results.add(invoke(method)); + } + + if (afterClass.size() != 0) { + afterClass.get(0).setAccessible(true); + afterClass.get(0).invoke(instance); + } + + return results; + } + + + /** + * + * @param time test run time + * @param className class name for test + * @param testName test name + * @param isFail test failure + * @param causeOfIgnoring reason for which the test was ignored + * @param e Exception that was thrown by the test + * @return information about test in interface TestResult + */ + private TestResult getResult(final long time, final String className, final String testName, + final boolean isFail, final String causeOfIgnoring, final Exception e) { + return new TestResult() { + @Override + public long getTime() { + return time; + } + + @Override + public String getClassName() { + return className; + } + + @Override + public String getTestName() { + return testName; + } + + @Override + public boolean isFail() { + return isFail; + } + + @Override + public String causeOfIgnoring() { + return causeOfIgnoring; + } + + @Override + public Exception getException() { + return e; + } + + }; + } + + + /** + * invoke method + * @param method method that will be called + * @return information about passing the test + * @throws IllegalAccessException if invoke threw IllegalAccessException + */ + private TestResult invoke(final Method method) throws IllegalAccessException { + final Test testAnnotation = method.getAnnotation(Test.class); + + if (!testAnnotation.ignore().equals("")) { + return getResult(0, testClassName, method.getName(), false, testAnnotation.ignore(), null); + } + + long startTimer = System.currentTimeMillis(); + Exception exception = null; + try { + if (before.size() != 0) { + before.get(0).setAccessible(true); + before.get(0).invoke(instance); + } + method.invoke(instance); + + if (after.size() != 0) { + after.get(0).setAccessible(true); + after.get(0).invoke(instance); + } + } catch (InvocationTargetException e) { + exception = (Exception) e.getCause(); + } + long endTimer = System.currentTimeMillis(); + + if ((exception != null && !testAnnotation.expected().isInstance(exception)) || + (exception == null && !testAnnotation.expected().equals(Test.IgnoredThrowable.class))) { + return getResult(endTimer - startTimer, testClassName, method.getName(), true, "", exception); + } + + return getResult(endTimer - startTimer, testClassName, method.getName(), false, "", exception); + + } + + /** + * Group methods with annotation in class + * @param testClazz class for test + * @throws TestExecutorException if wrong number methods with annotation + */ + private void getMethods(Class testClazz) throws TestExecutorException { + Class[] classes = {After.class, AfterClass.class, Before.class, BeforeClass.class, Test.class}; + List> lists = new ArrayList<>(); + lists.add(after); + lists.add(afterClass); + lists.add(before); + lists.add(beforeClass); + lists.add(tests); + for (Method method : testClazz.getDeclaredMethods()) { + boolean flag = false; + for (int i = 0; i < classes.length; i++) { + if (method.getAnnotation(classes[i]) != null) { + if (flag) { + throw new TestExecutorException("too much annotations for method " + method.getName()); + } + lists.get(i).add(method); + flag = true; + } + } + } + for (int i = 0; i < lists.size() - 1; i++) { + if (lists.get(i).size() > 1) { + throw new TestExecutorException("too much methods with annotation " + classes[i].getName()); + } + } + } + + +} diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestExecutorException.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestExecutorException.java new file mode 100644 index 0000000..3b8ed0d --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestExecutorException.java @@ -0,0 +1,11 @@ +package ru.spbau.mit.alyokhina; + +/** + * Exception for Test Executor + * Throws if wrong number methods with annotation + */ +public class TestExecutorException extends Exception { + public TestExecutorException(String msg) { + super(msg); + } +} diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestResult.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestResult.java new file mode 100644 index 0000000..59f4f8e --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/TestResult.java @@ -0,0 +1,22 @@ +package ru.spbau.mit.alyokhina; + +/** Interface for information about passing the test */ +public interface TestResult { + /** Time of the test */ + long getTime(); + + /** The name of the class in which the test was called */ + String getClassName(); + + /** The test name */ + String getTestName(); + + /** Test failure */ + boolean isFail(); + + /** The reason for which the test was ignored */ + String causeOfIgnoring(); + + /** The exception that was thrown by the test */ + Exception getException(); +} diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/After.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/After.java new file mode 100644 index 0000000..60416e3 --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/After.java @@ -0,0 +1,12 @@ +package ru.spbau.mit.alyokhina.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation of the method that will be run after each test */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface After { +} diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/AfterClass.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/AfterClass.java new file mode 100644 index 0000000..07ffb51 --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/AfterClass.java @@ -0,0 +1,12 @@ +package ru.spbau.mit.alyokhina.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation of the method that will be run after the test runs */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AfterClass { +} diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/Before.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/Before.java new file mode 100644 index 0000000..59a7db8 --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/Before.java @@ -0,0 +1,12 @@ +package ru.spbau.mit.alyokhina.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation of the method that will be run before each test */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Before { +} diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/BeforeClass.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/BeforeClass.java new file mode 100644 index 0000000..4af9a21 --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/BeforeClass.java @@ -0,0 +1,12 @@ +package ru.spbau.mit.alyokhina.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation of the method that will be run before the test runs */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface BeforeClass { +} diff --git a/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/Test.java b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/Test.java new file mode 100644 index 0000000..efcd09c --- /dev/null +++ b/05.XUnit/src/main/java/ru/spbau/mit/alyokhina/annotation/Test.java @@ -0,0 +1,21 @@ +package ru.spbau.mit.alyokhina.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for the test. + * Annotation can have two arguments - expected (for excepion) , ignore - to cancel the start and specify the cause + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Test { + String ignore() default ""; + + Class expected() default IgnoredThrowable.class; + + class IgnoredThrowable extends Throwable { + } +} diff --git a/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestIgnored.java b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestIgnored.java new file mode 100644 index 0000000..419774b --- /dev/null +++ b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestIgnored.java @@ -0,0 +1,33 @@ +package ru.spbau.mit.alyokhina.ClassForTests; + +import ru.spbau.mit.alyokhina.annotation.Test; + +import java.io.IOException; + +public class TestIgnored { + static int count = 0; + @Test(ignore = "I want and I ignore") + protected void test1() { + count++; + throw new NullPointerException(); + } + + @Test(ignore = "And this test I ignore") + void test2() { + count++; + throw new NullPointerException(); + } + + @Test(ignore = "And this", expected = NullPointerException.class) + private void test3() { + count++; + throw new NullPointerException(); + } + + @Test( expected = IOException.class, ignore = "And this I don't like") + public void test4() { + count++; + throw new NullPointerException(); + } + +} diff --git a/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithBeforeAndAfter.java b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithBeforeAndAfter.java new file mode 100644 index 0000000..88eb263 --- /dev/null +++ b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithBeforeAndAfter.java @@ -0,0 +1,32 @@ +package ru.spbau.mit.alyokhina.ClassForTests; + +import ru.spbau.mit.alyokhina.annotation.*; + + +public class TestWithBeforeAndAfter { + public static int countBefore = 0, countAfter = 0; + @Before + void before() { + countBefore++; + } + + @After + void after() { + countAfter++; + } + + @Test + void test1() { + throw new NullPointerException(); + } + + @Test + void test2() { + int x = 1; + } + + @Test + void test3() { + int x = 1; + } +} diff --git a/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithBeforeClassAndAfterClass.java b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithBeforeClassAndAfterClass.java new file mode 100644 index 0000000..e14a6fc --- /dev/null +++ b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithBeforeClassAndAfterClass.java @@ -0,0 +1,31 @@ +package ru.spbau.mit.alyokhina.ClassForTests; + +import ru.spbau.mit.alyokhina.annotation.*; + +public class TestWithBeforeClassAndAfterClass { + public static int countBeforeClass = 0, countAfterClass = 0; + @BeforeClass + void before() { + countBeforeClass++; + } + + @AfterClass + void after() { + countAfterClass++; + } + + @Test + void test1() { + throw new NullPointerException(); + } + + @Test + void test2() { + int x = 1; + } + + @Test + void test3() { + int x = 1; + } +} diff --git a/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithException.java b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithException.java new file mode 100644 index 0000000..595b174 --- /dev/null +++ b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/ClassForTests/TestWithException.java @@ -0,0 +1,32 @@ +package ru.spbau.mit.alyokhina.ClassForTests; + +import ru.spbau.mit.alyokhina.annotation.Test; + +import java.io.IOException; + +public class TestWithException { + + @Test + private void test1() { + throw new NullPointerException(); + } + + @Test(expected = NullPointerException.class) + private void test2() { + throw new NullPointerException(); + } + + @Test(expected = IOException.class) + private void test3() { + throw new NullPointerException(); + } + + @Test(expected = NullPointerException.class) + private void test4() { + } + + @Test + private void test5() { + + } +} diff --git a/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/TestExecutorTest.java b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/TestExecutorTest.java new file mode 100644 index 0000000..9768d92 --- /dev/null +++ b/05.XUnit/src/test/java/ru/spbau/mit/alyokhina/TestExecutorTest.java @@ -0,0 +1,94 @@ +package ru.spbau.mit.alyokhina; + +import org.junit.Test; + +import java.lang.reflect.InvocationTargetException; +import java.util.Comparator; +import java.util.List; + +import ru.spbau.mit.alyokhina.ClassForTests.TestWithBeforeAndAfter; +import ru.spbau.mit.alyokhina.ClassForTests.TestWithBeforeClassAndAfterClass; + +import static org.junit.Assert.*; + +public class TestExecutorTest { + + @Test + public void testWithBeforeAndAfter() throws ClassNotFoundException, InvocationTargetException, + IllegalAccessException, InstantiationException, TestExecutorException { + TestExecutor testExecutor = new TestExecutor(Class.forName("ru.spbau.mit.alyokhina.ClassForTests.TestWithBeforeAndAfter")); + List results = testExecutor.run(); + assertEquals(3, results.size()); + assertEquals(3, TestWithBeforeAndAfter.countBefore); + assertEquals(2, TestWithBeforeAndAfter.countAfter); + } + + @Test + public void testWithBeforeClassAndAfterClass() throws ClassNotFoundException, InvocationTargetException, + IllegalAccessException, InstantiationException, TestExecutorException { + TestExecutor testExecutor = new TestExecutor(Class.forName("ru.spbau.mit.alyokhina.ClassForTests.TestWithBeforeClassAndAfterClass")); + List results = testExecutor.run(); + assertEquals(3, results.size()); + assertEquals(1, TestWithBeforeClassAndAfterClass.countBeforeClass); + assertEquals(1, TestWithBeforeClassAndAfterClass.countAfterClass); + + } + + @Test + public void testWithException() throws ClassNotFoundException, InvocationTargetException, + IllegalAccessException, InstantiationException, TestExecutorException { + TestExecutor testExecutor = new TestExecutor(Class.forName("ru.spbau.mit.alyokhina.ClassForTests.TestWithException")); + List results = testExecutor.run(); + assertEquals(5, results.size()); + results.sort(Comparator.comparing(TestResult::getTestName)); + + assertEquals("test1", results.get(0).getTestName()); + assertEquals(true, results.get(0).isFail()); + assertTrue(results.get(0).getException() instanceof NullPointerException); + assertTrue(results.get(0).causeOfIgnoring().equals("")); + + assertEquals("test2", results.get(1).getTestName()); + assertEquals(false, results.get(1).isFail()); + assertTrue(results.get(1).getException() instanceof NullPointerException); + assertTrue(results.get(1).causeOfIgnoring().equals("")); + + assertEquals("test3", results.get(2).getTestName()); + assertEquals(true, results.get(2).isFail()); + assertTrue(results.get(2).getException() instanceof NullPointerException); + assertTrue(results.get(2).causeOfIgnoring().equals("")); + + assertEquals("test4", results.get(3).getTestName()); + assertEquals(true, results.get(3).isFail()); + assertTrue(results.get(3).getException() == null); + assertTrue(results.get(3).causeOfIgnoring().equals("")); + + assertEquals("test5", results.get(4).getTestName()); + assertEquals(false, results.get(4).isFail()); + assertTrue(results.get(4).getException() == null); + assertTrue(results.get(4).causeOfIgnoring().equals("")); + } + + @Test + public void testWithIgnore() throws ClassNotFoundException, InvocationTargetException, + IllegalAccessException, InstantiationException, TestExecutorException { + TestExecutor testExecutor = new TestExecutor(Class.forName("ru.spbau.mit.alyokhina.ClassForTests.TestIgnored")); + List results = testExecutor.run(); + assertEquals(4, results.size()); + results.sort(Comparator.comparing(TestResult::getTestName)); + + assertEquals("test1", results.get(0).getTestName()); + assertFalse(results.get(0).causeOfIgnoring().equals("")); + + assertEquals("test2", results.get(1).getTestName()); + assertFalse(results.get(1).causeOfIgnoring().equals("")); + + assertEquals("test3", results.get(2).getTestName()); + assertFalse(results.get(2).causeOfIgnoring().equals("")); + + assertEquals("test4", results.get(3).getTestName()); + assertFalse(results.get(3).causeOfIgnoring().equals("")); + + + } + +} \ No newline at end of file diff --git a/buildscript.sh b/buildscript.sh new file mode 100755 index 0000000..1cc07e7 --- /dev/null +++ b/buildscript.sh @@ -0,0 +1,9 @@ +#!/bin/bash +files=$(find . -maxdepth 1 -type d | grep "./0.*") +for file in $files +do + cd $file + mvn test -B + cd ../ +done +