Java 用于写入文件的JUnit测试在本地通过,但在Travis CI上失败

Java 用于写入文件的JUnit测试在本地通过,但在Travis CI上失败,java,junit,travis-ci,Java,Junit,Travis Ci,问题 老师指示我和我的小组成员为我们的游戏实现一个记录器。这个记录器应该使用JUnit进行良好的测试,我们也是这样做的。我们目前正在处理的一个问题是,所有这些测试都在本地通过,但在Travis CI上时不时会失败 我们的分析 我们怀疑在执行断言之前,没有足够的时间让这些测试实际创建和删除日志文件。然而,我们并不确定这是否是导致我们在Travis上测试失败的原因 我们的代码 Logger.java [……] 编辑#1 我已经使用了,但Travis仍在努力创建/删除文件。即使有一分钟的超时 Logg

问题

老师指示我和我的小组成员为我们的游戏实现一个记录器。这个记录器应该使用JUnit进行良好的测试,我们也是这样做的。我们目前正在处理的一个问题是,所有这些测试都在本地通过,但在Travis CI上时不时会失败

我们的分析

我们怀疑在执行断言之前,没有足够的时间让这些测试实际创建和删除日志文件。然而,我们并不确定这是否是导致我们在Travis上测试失败的原因

我们的代码

Logger.java

[……]

编辑#1

我已经使用了,但Travis仍在努力创建/删除文件。即使有一分钟的超时

LoggerTest.java


在第一个测试用例中,您没有给记录器线程足够的时间来创建文件。这在某些操作系统上可能有效,但Travis CI的速度很慢。建议:创建一种方法,以一定间隔(例如100ms)轮询一段时间(至少5秒)的条件(在本例中,文件存在)

private静态布尔pollForCondition(可调用条件){
而(/*…*/){
if(condition.call().booleanValue()){
返回true;
}
// ...
}
返回false;
}
此方法应在所有测试用例中使用(在
assertTrue()
中)


还考虑到测试用例的执行顺序没有确定(从java 6或7开始,AFAIK)。您是否在下一个测试用例开始之前删除了创建的文件?

我尝试应用您的建议,但没有成功。你能再看一下我的编辑吗?你检查过我的最后一点了吗?Travis的执行令可能与您的本地执行令不同。尝试一个接一个地在本地执行测试,而不在中间进行清理,并检查它们在以相反顺序执行时是否工作。顺序test_logcreate、test_logdelete、test_capLog、test_getter、test_debugMode似乎在本地工作。但是顺序test_capLog、test_debugMode、test_getter、test_logcreate、test_logdelete失败。这是您的问题。阅读JUnit中有关前后方法的内容。您必须使用Before方法设置一个“干净”的环境,该方法适用于每种测试方法。
package logging;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.sql.Timestamp;
import java.util.Calendar;

public final class Logger extends Thread {
    private static volatile boolean debug = false;
    private static volatile StringBuilder queue = new StringBuilder();
    private static volatile File file = new File("log.log");
    private static volatile int logLength = 10000;
    private static Logger logger;

    /**
     * supported logging types.
     *
     */
    public enum LogType {
        INFO, WARNING, ERROR
    }

    private Logger() {
    } // unreachable because static

    /**
     * The logger runs in his own thread to prevent concurrent writing
     * exceptions on the log file if multiple threads are logging.
     */
    public void run() {
        while (debug) {
            try {
                sleep(10000);
                if (queue.length() > 0) {
                    try {
                        writeToFile();
                    } catch (IOException exception) {
                        exception.printStackTrace();
                    }
                }
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }

    private void writeToFile() throws IOException {
        if (!file.exists()) {
            file.createNewFile();
        }
        FileWriter writer = new FileWriter(file, true);
        writer.write(queue.toString());
        writer.close();
        capFileSize();
    }

    private void capFileSize() throws IOException {
        int fileLength = countLines();
        if (fileLength > logLength) {
            String line;
            File tempFile = File.createTempFile("TETRIS_LOG_", ".log");
            BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile));
            try {
                BufferedReader reader = new BufferedReader(new FileReader(file));
                try {
                    skipLines(fileLength - logLength, reader);
                    while ((line = reader.readLine()) != null) {
                        writer.write(line);
                        writer.newLine();
                    }
                } finally {
                    reader.close();
                }
            } finally {
                writer.close();
            }
            Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
        }
    }

    private void skipLines(int lines, BufferedReader file) throws IOException {
        for (int i = 0; i < lines; i++) {
            file.readLine();
        }
    }

    private int countLines() throws IOException {
        char[] buffer = new char[1024];
        int count = 0;
        int readChars = 0;
        boolean empty = true;
        BufferedReader reader = new BufferedReader(new FileReader(file));
        try {
            while ((readChars = reader.read(buffer)) != -1) {
                empty = false;
                for (int i = 0; i < readChars; ++i) {
                    if (buffer[i] == '\n') {
                        ++count;
                    }
                }
            }
            return (count == 0 && !empty) ? 1 : count;
        } finally {
            reader.close();
        }
    }

    /**
     * Log lets you log a line in the log file, conditional on the debug mode
     * being on.
     * 
     * @param sender
     *            the object invoking the log statement
     * @param logtype
     *            the log type, for homogeneity constrained in the LogType enum
     * @param message
     *            the message that is logged
     */
    public static void log(Object sender, LogType logtype, String message) {
        if (debug) {
            String msg = String.format("[%s] message @[%s] from object %s: %s\r\n",
                logtype.toString(),
                new Timestamp(Calendar.getInstance().getTimeInMillis()).toString(),
                sender.toString(), message);
            queue.append(msg);
        }
    }

    public static void info(Object sender, String message) {
        Logger.log(sender, LogType.INFO, message);
    }

    public static void error(Object sender, String message) {
        Logger.log(sender, LogType.ERROR, message);
    }

    public static void warning(Object sender, String message) {
        Logger.log(sender, LogType.WARNING, message);
    }

    /**
     * clearLog truncates the log, in case you accidentally logged a nude
     * picture or something.
     */
    public static void clearLog() {
        try {
            Files.deleteIfExists(file.toPath());
        } catch (IOException exception) {
            exception.printStackTrace();
        }
    }

    /* getters / setters */
    public static int getLogLength() {
        return logLength;
    }

    public static void setLogLength(int length) {
        logLength = length;
    }

    public static void setLogDir(String path) {
        file = new File(path);
    }

    public static String getLogDir() {
        return file.toString();
    }

    /**
     * switch debug on.
     */
    public static synchronized void setDebugOn() {
        if (!debug) {
            debug = true;
            logger = new Logger();
            logger.start();
        }
    }

    /**
     * switch debug off.
     */
    public static void setDebugOff() {
        if (debug) {
            debug = false;
            try {
                logger.join();
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
        logger = null;
    }
}
package logging;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class LoggerTest {
    @Test
    public void test_logcreate() {
        String testloc = "test.log";
        Logger.setLogDir(testloc);
        Logger.clearLog();
        Logger.setDebugOn();
        Logger.log(this, Logger.LogType.ERROR, "test 1");
        Logger.setDebugOff();
        assertTrue(new File(testloc).exists());
    }

    @Test
    public void test_logdelete() {
        String testloc = "test.log";
        Logger.setLogDir(testloc);
        Logger.setDebugOn();
        Logger.log(this, Logger.LogType.ERROR, "test 1");
        assertTrue(new File(testloc).exists());
        Logger.setDebugOff();
        Logger.clearLog();
        assertFalse(new File(testloc).exists());
    }

    @Test
    public void test_debugMode() {
        String testloc = "test.log";
        Logger.setLogDir(testloc);
        Logger.setDebugOff();
        Logger.clearLog();
        Logger.log(this, Logger.LogType.ERROR, "test 1");

        assertFalse(new File(testloc).exists());
    }

    @Test
    public void test_capLog() throws IOException, InterruptedException {
        String testloc = "test.log";
        Logger.setLogDir(testloc);
        Logger.setLogLength(10);
        Logger.clearLog();
        Logger.setDebugOn();
        for (int i = 0; i < 100; i++) {
            Logger.log(this, Logger.LogType.ERROR, "test 1");
        }
        Logger.setDebugOff();
        Thread.sleep(1000);
        assertTrue(new File(testloc).exists());

        int count = 0;
        File file = new File(testloc);
        FileReader fileReader = new FileReader(file);
        BufferedReader reader = new BufferedReader(fileReader);
        while (reader.readLine() != null) {
            ++count;
        }
        reader.close();
        assertEquals(10, count);

    }
}
:test
Download https://jcenter.bintray.com/org/jacoco/org.jacoco.agent/0.7.7.201606060606/org.jacoco.agent-0.7.7.201606060606.pom
Download https://jcenter.bintray.com/org/jacoco/org.jacoco.build/0.7.7.201606060606/org.jacoco.build-0.7.7.201606060606.pom
Download https://jcenter.bintray.com/org/jacoco/org.jacoco.agent/0.7.7.201606060606/org.jacoco.agent-0.7.7.201606060606.jar
tetris.LoggerTest > test_logcreate FAILED
    java.lang.AssertionError at LoggerTest.java:26
tetris.LoggerTest > test_logdelete FAILED
    java.lang.AssertionError at LoggerTest.java:35
47 tests completed, 2 failed
:test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
package tetris;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.concurrent.Callable;

import org.junit.Test;

import static com.jayway.awaitility.Awaitility.with;
import static com.jayway.awaitility.Duration.*;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import logging.Logger;

public class LoggerTest {

    @Test
    public void test_logcreate() throws Exception {
        String testloc = "test.log";
        Logger.setLogDir(testloc);
        Logger.clearLog();
        Logger.setDebugOn();
        Logger.log(this, Logger.LogType.ERROR, "test 1");
        Logger.setDebugOff();

        asyncWaitForFileCreation(testloc);
        assertTrue(new File(testloc).exists());
    }

    @Test
    public void test_logdelete() throws Exception {
        String testloc = "test.log";
        Logger.setLogDir(testloc);
        Logger.setDebugOn();
        Logger.log(this, Logger.LogType.ERROR, "test 1");

        asyncWaitForFileCreation(testloc);
        assertTrue(new File(testloc).exists());

        Logger.setDebugOff();
        Logger.clearLog();

        asyncWaitForFileRemoval(testloc);
        assertFalse(new File(testloc).exists());
    }

    @Test
    public void test_debugMode() throws Exception {
        String testloc = "test.log";
        Logger.setLogDir(testloc);
        Logger.setDebugOff();
        Logger.clearLog();
        Logger.log(this, Logger.LogType.ERROR, "test 1");

        asyncWaitForFileRemoval(testloc);
        assertFalse(new File(testloc).exists());
    }

    @Test
    public void test_capLog() throws Exception {
        String testloc = "test.log";
        Logger.setLogDir(testloc);
        Logger.setLogLength(10);
        Logger.clearLog();
        Logger.setDebugOn();
        for (int i = 0; i < 100; i++) {
            Logger.log(this, Logger.LogType.ERROR, "test 1");
            Logger.info(this, "test 1");
            Logger.warning(this, "test 1");
            Logger.error(this, "test 1");
        }
        Logger.setDebugOff();

        File testlocFile = new File(testloc);

        asyncWaitForFileCreation(testloc);
        assertTrue(testlocFile.exists());

        int count = 0;
        File file = new File(testloc);
        FileReader fileReader = new FileReader(file);
        BufferedReader reader = new BufferedReader(fileReader);
        while (reader.readLine() != null) {
            ++count;
        }
        reader.close();

        assertEquals(10, count);
    }
    @Test
    public void test_getters() throws ClassCastException {
        assertTrue(Logger.getLogDir() instanceof String);
        assertTrue(Logger.getLogLength() == Logger.getLogLength());
    }

    private void asyncWaitForFileCreation(String testloc) throws Exception {
        with().pollDelay(ONE_HUNDRED_MILLISECONDS)
                .and().with().pollInterval(TWO_HUNDRED_MILLISECONDS)
                .and().with().timeout(ONE_MINUTE)
                .await("file creation")
                .until(fileIsCreatedOnDisk(testloc), equalTo(true));
    }

    private void asyncWaitForFileRemoval(String testloc) throws Exception {
        with().pollDelay(ONE_HUNDRED_MILLISECONDS)
                .and().with().pollInterval(TWO_HUNDRED_MILLISECONDS)
                .and().with().timeout(ONE_MINUTE)
                .await("file removal")
                .until(fileIsRemovedFromDisk(testloc), equalTo(true));
    }

    private Callable<Boolean> fileIsCreatedOnDisk(final String filename) {
        return () -> {
            File file = new File(filename);
            return file.exists();
        };
    }

    private Callable<Boolean> fileIsRemovedFromDisk(final String filename) {
        return () -> {
            File file = new File(filename);
            return !file.exists();
        };
    }
}
:test
Download https://jcenter.bintray.com/org/jacoco/org.jacoco.agent/0.7.7.201606060606/org.jacoco.agent-0.7.7.201606060606.pom
Download https://jcenter.bintray.com/org/jacoco/org.jacoco.build/0.7.7.201606060606/org.jacoco.build-0.7.7.201606060606.pom
Download https://jcenter.bintray.com/org/jacoco/org.jacoco.agent/0.7.7.201606060606/org.jacoco.agent-0.7.7.201606060606.jar
tetris.LoggerTest > test_logcreate FAILED
    java.util.concurrent.TimeoutException at LoggerTest.java:36
tetris.LoggerTest > test_capLog FAILED
    java.util.concurrent.TimeoutException at LoggerTest.java:94
47 tests completed, 2 failed
:test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
private static boolean pollForCondition(Callable<Boolean> condition) {
  while (/* ... */) {
    if (condition.call().booleanValue()) {
      return true;
    }
    // ...
  }
  return false;
}