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 extends Throwable> 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
+