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
?