File.exists() иногда неправильно в Windows 10 с Java 8.191

У меня есть куча модульных тестов, которые содержат такой код:

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

Этот тест неожиданно -DforkCount=0 неудачей при запуске с Maven Surefire и -DforkCount=0. С -DforkCount=1 это работает.

Вещи, которые я пробовал до сих пор:

  • Файл существует. Проводник Windows, командная строка (копирование и вставка), текстовые редакторы, Cygwin могут все найти и показать содержимое. Вот почему я думаю, что это не проблема разрешения.
  • Это не изменено модульными тестами или чем-то еще. Git не показывает изменений за последние два месяца.
  • Я проверил файловую систему, она чистая.
  • Я пробовал другие версии Java 8, а именно 8u171 и 8u181. Та же проблема.
  • Я запустил Maven из Cygwin и командной строки. Тот же результат.
  • Перезагрузка :-) Нет эффекта :-(

Больше деталей:

  • Когда я вижу эту проблему, я начинаю видеть, что "разветвленная виртуальная машина прервалась, не сказав должным образом до свидания. Сбой виртуальной машины или вызванный System.exit?" в других проектах. Вот почему я попытался использовать forkCount=0 что часто помогает в этом случае выяснить, почему разбилась разветвленная виртуальная машина.
  • Это началось недавно, возможно, в октябре 2018 года, в обновлении Windows 10. До этого сборки были непоколебимы в течение трех лет. Думаю, моя машина была переведена на Windows 10 в конце 2017 года.
  • Я использую Maven 3.6 и не могу легко попробовать старую версию из-за важной ошибки, которая была исправлена с ней. Я видел сбой VM выше с Maven 3.5.2, а также.
  • Это всегда одни и те же файлы, которые выходят из строя (поэтому он стабильный).

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

Бернхард попросил меня напечатать абсолютные имена. Мой ответ состоял в том, что я уже использовал абсолютные имена, но я ошибался. Фактический код был:

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 создает разветвленную виртуальную машину для запуска тестов с Surefire, он может изменить текущий каталог. Таким образом, в этом случае имеет смысл, чтобы тесты работали при разветвлении, но не работали при работе в той же виртуальной машине (поскольку виртуальная машина была создана в корневой папке многомодульной сборки). Но почему сделать путь абсолютным до того, как вызов функции exists() решит проблему?

Ответ 1

Некоторый фон. У каждого процесса есть понятие "текущий каталог". Когда запускается из командной строки, то это каталог, в котором была выполнена команда. При запуске из пользовательского интерфейса обычно это папка, в которой находится программа (файл .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") для определения пути, а первый будет использовать функцию API Windows _wgetdcwd (docs), которая, в свою очередь, использует поле процесса Windows для получить текущий каталог - в нашем случае это всегда папка, в которой изначально был запущен Maven, потому что Java не обновляет поле в процессе, когда кто-то изменяет user.dir а 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/surefire-common/src/main/java/org/Maven/плагин/безошибочный /AbstractSurefireMojo.java # L1060

Когда он не разветвляется, он копируется в текущие свойства системы VM: https://github.com/apache/maven-surefire/blob/56d41b4c903b6c134c5e1a2891f9f08be7e5039f/maven-surefire-common/src/main/java/org/apache/maven//surefire/AbstractSurefireMojo.java#L1133

Ответ 2

Аарон, мы разрабатываем Surefire. Мы можем помочь вам, если вы укажете путь для этого:

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

Просьба опубликовать фактический путь, ожидаемый путь и basedir, где находится ваш POM. Теория здесь не поможет. Мы тестируем весь спектр JDK 7-12, но у нас нет комбинации Cygwin + Windows, которую следует учитывать. Настройка кода user.dir в упомянутом вами Surefire существует десятилетие.

Ответ 3

Поэтому я проверил распечатку системных свойств с помощью нескольких простых тестов.

Во время тестов через maven-surefire-plugin user.dir будет изменен на корень соответствующего модуля в многокомпонентной сборке.

Но, как я уже упоминал, есть системное свойство, доступное basedir которое можно использовать для правильной обработки местоположения для тестов, которым необходим доступ к ним через File... basedir указывает на местоположение pom.xml соответствующего модуля.

Но, к сожалению, свойство basedir не устанавливается IDEA IntelliJ во время выполнения теста.

Но это можно решить с помощью такой установки:

 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());
 }

Это будет работать в Maven Surefire с -DforkCount=0 а также -DforkCount=1 и в IDE (проверено только IDEA IntelliJ).

И да, это проблема в плагине Maven Surefire при изменении user.dir.

Мы можем убедить IDE в поддержке свойства basedir?