在具有Java 8.191的Windows 10上,File.exists()有时会出错

时间:2019-01-25 13:29:37

标签: java-8 windows-10

我有一堆包含如下代码的单元测试:

File file = new File("src/main/java/com/pany/Foo.java");
assertTrue("Missing file: " + file.getAbsolutePath(), file.exists());

使用Maven Surefire和-DforkCount=0运行该测试时,该测试突然失败。使用-DforkCount=1,它可以工作。

到目前为止我尝试过的事情:

  • 该文件确实存在。 Windows资源管理器,命令行(复制和粘贴),文本编辑器,Cygwin都可以找到它并显示其内容。这就是为什么我认为这不是许可问题。
  • 未经单元测试或其他任何修改。 Git在过去两个月中未显示任何修改。
  • 我已经检查了文件系统,它很干净。
  • 我尝试了Java 8的其他版本,即8u171和8u181。同样的问题。
  • 我已经在Cygwin和命令提示符中运行Maven。结果相同。
  • 重新启动:-)无效:-(

更多详细信息:

  • 当我看到此问题时,我开始看到“分叉的VM终止而没有正确说再见。VM崩溃还是调用了System.exit?”在其他项目中。这就是为什么我尝试forkCount=0的原因,在这种情况下,该方法通常可以帮助您了解为什么分叉的VM崩溃了。
  • 这是最近才开始的,也许是在Windows 10的2018年10月更新前后。在此之前,构建工作已经进行了大约三年。我想我的机器在2017年末切换到Windows 10。
  • 我正在使用Maven 3.6,由于已修复了一个重要的错误,因此无法轻松尝试使用旧版本。我的确在Maven 3.5.2上也看到了VM崩溃。
  • 总是失败的文件相同(因此很稳定)。

ulimit(来自Cygwin)说:

$ ulimit -a
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
file size               (blocks, -f) unlimited
open files                      (-n) 256
pipe size            (512 bytes, -p) 8
stack size              (kbytes, -s) 2032
cpu time               (seconds, -t) unlimited
max user processes              (-u) 256
virtual memory          (kbytes, -v) unlimited

我想知道256个“打开文件”限制是否仅适用于Cygwin进程,还是Cygwin从Windows读取的内容?

让我知道您是否还有其他需要。我没有办法尝试的想法。

更新1

Bernhard要求我打印绝对名称。我的回答是我已经在使用绝对名称,但是我错了。实际的代码是:

File file = new File("src/main/java/com/pany/Foo.java");
if (!file.exists()) {
    log.debug("Missing file {}", file.getAbsolutePath());
    ... fail ...
}

... do something with file...

我现在将其更改为:

File file = new File("src/main/java/com/pany/Foo.java").getAbsoluteFile();
if (!file.exists()) {
    log.debug("Missing file {}", file);
}

并解决了该问题。我只是不明白为什么。

当Maven创建一个分叉的VM以使用Surefire运行测试时,它可以更改当前目录。因此,在这种情况下,有意义的是,测试在分支时可以工作,但在同一VM中运行时将失败(因为VM是在多模块构建的根文件夹中创建的)。但是,为什么在调用exists()之前将路径设为绝对路径才能解决此问题?

3 个答案:

答案 0 :(得分:8)

一些背景。每个进程都有“当前目录”的概念。从命令行启动时,它就是执行命令的目录。从UI启动时,通常是程序(.exe文件)所在的文件夹。

在命令提示符或BASH中,您可以使用cd更改此文件夹,以运行命令提示符。

Maven构建多模块项目时,必须为每个模块更改此设置(以便相对路径src/main/java/始终指向正确的位置)。不幸的是,Java在任何地方都没有“设置当前目录”方法。创建新流程时只能指定一个,并且可以修改系统属性user.dir

这就是new File("a").exists()new File("a").getAbsoluteFile().exists()工作不同的原因。

后者将使用new File(System.getProperty("user.dir"), "a")确定路径,而前者将使用Windows API函数_wgetdcwddocs),该函数依次使用Windows进程的字段来获取路径。当前目录-在我们的例子中,始终是Maven最初所在的文件夹,因为当有人更改user.dir时Java不会更新进程中的字段,并且Maven只能将此属性更改为“模拟”更改的文件夹。

WinNTFileSystem_md.c呼叫fileToNTPath()。这是在io_util_md.c中定义的,并调用pathToNTPath()。对于相对路径,它将调用currentDirLength(),后者调用currentDir(),后者_wgetdcwd()

另请参阅:

这是Surefire插件修改属性user.dir的地方:https://github.com/apache/maven-surefire/blob/56d41b4c903b6c134c5e1a2891f9f08be7e5039f/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java#L1060

不进行分叉时,会将其复制到当前VM的系统属性中:https://github.com/apache/maven-surefire/blob/56d41b4c903b6c134c5e1a2891f9f08be7e5039f/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java#L1133

答案 1 :(得分:1)

所以我have checked via printing out the system properties with some simple tests

在通过maven-surefire-plugin测试期间,user.dir将更改为多模块构建中相应模块的根。

但是正如我已经提到的,有一个可用的系统属性basedir可用于正确处理需要通过File访问它们的测试的位置... basedir指向相应模块的pom.xml的位置。

但是很遗憾,IDEA IntelliJ在测试运行期间未设置basedir属性。

但这可以通过以下设置解决:

 private String basedir;

 @Before
 public void before() {
    this.basedir = System.getProperty("basedir", System.getProperty("user.dir", "Need to think about the default value"));
 }

 @Test
 public void testX() {
   File file = new File(this.basedir, "src/main/java/com/pany/Foo.java");
   assertTrue("Missing file: " + file.getAbsolutePath(), file.exists());
 }

这将在-DforkCount=0-DforkCount=1的Maven Surefire中以及在IDE(仅选中IDEA IntelliJ)中使用。

是的,这是Maven Surefire插件中更改user.dir的问题。

我们可以说服IDE也支持basedir属性吗?

答案 2 :(得分:0)

亚伦,我们研发了Surefire。如果您提供以下方法,我们可以为您提供帮助:

assertTrue("Missing file: " + file.getAbsolutePath(), file.exists());

请发布您的POM所在的实际路径,预期路径和 basedir 。 该理论在这里无济于事。我们正在测试JDK 7-12的所有范围,但没有必须考虑的Cygwin + Windows组合。 您提到的Surefire中的代码设置 user.dir 存在了十年。