cucumber / cucumber-jvm

Cucumber for the JVM
https://cucumber.io
MIT License
2.7k stars 2.02k forks source link

Confusing/misleading error creating FileOutputStream for plugin #2108

Open aslakhellesoy opened 4 years ago

aslakhellesoy commented 4 years ago

Someone contacted me with the following stack trace:

FAILED CONFIGURATION: @BeforeClass setUpClass
java.lang.IllegalArgumentException: Couldn't create a file output stream for target\cucumber\cucumberReport.json.
Make sure the the file isn't a directory.
The details are in the stack trace below:
at io.cucumber.core.plugin.PluginFactory.createFileOutputStream(PluginFactory.java:209)
at io.cucumber.core.plugin.PluginFactory.openStream(PluginFactory.java:197)
at io.cucumber.core.plugin.PluginFactory.convert(PluginFactory.java:164)
at io.cucumber.core.plugin.PluginFactory.instantiate(PluginFactory.java:97)
at io.cucumber.core.plugin.PluginFactory.create(PluginFactory.java:62)
at io.cucumber.core.plugin.Plugins.createPlugins(Plugins.java:32)
at io.cucumber.core.plugin.Plugins.<init>(Plugins.java:25)
at io.cucumber.testng.TestNGCucumberRunner.<init>(TestNGCucumberRunner.java:92)
at io.cucumber.testng.AbstractTestNGCucumberTests.setUpClass(AbstractTestNGCucumberTests.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:134)
at org.testng.internal.MethodInvocationHelper.invokeMethodConsideringTimeout(MethodInvocationHelper.java:63)
at org.testng.internal.ConfigInvoker.invokeConfigurationMethod(ConfigInvoker.java:348)
at org.testng.internal.ConfigInvoker.invokeConfigurations(ConfigInvoker.java:302)
at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:176)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:122)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.testng.TestRunner.privateRun(TestRunner.java:766)
at org.testng.TestRunner.run(TestRunner.java:587)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:384)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:378)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:337)
at org.testng.SuiteRunner.run(SuiteRunner.java:286)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:96)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1187)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1109)
at org.testng.TestNG.runSuites(TestNG.java:1039)
at org.testng.TestNG.run(TestNG.java:1007)
at org.testng.remote.AbstractRemoteTestNG.run(AbstractRemoteTestNG.java:115)
at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:251)
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:77)
Caused by: java.io.FileNotFoundException: target\cucumber\cucumberReport.json (The system cannot find the path specified)
at java.io.FileOutputStream.open0(Native Method)
at java.io.FileOutputStream.open(FileOutputStream.java:270)
at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
at java.io.FileOutputStream.<init>(FileOutputStream.java:162)
at io.cucumber.core.plugin.PluginFactory.createFileOutputStream(PluginFactory.java:207)
... 34 more

Some theories what might be wrong (without having looked at the code or tried to reproduce it):

In any case we should inspect the file system more closely when an error occurs so we can provide a better error message.

mpkorstanje commented 4 years ago

Without a reproducer or some triangulation it'd say it's impossible to tell. The javadoc for FileOutputStream is:

     * @exception  FileNotFoundException  if the file exists but is a directory
     *                   rather than a regular file, does not exist but cannot
     *                   be created, or cannot be opened for any other reason

So for example do any of these paths work:

target\cucumber\cucumberReport.json target\cucumberReport.json cucumberReport.json

And do any of the above paths work when the file/folders already exists?

We're not doing mkdir -p

No. We are creating the parent directories:

    private static FileOutputStream createFileOutputStream(File file) {
        try {
            File parentFile = file.getParentFile();
            if (parentFile != null) {
                parentFile.mkdirs();
            }
            return new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            throw new IllegalArgumentException(String.format("" +
                    "Couldn't create a file output stream for %s.\n" +
                    "Make sure the the file isn't a directory.\n" +
                    "The details are in the stack trace below:",
                file),
                e);
        }
    }
ITMANUPAM commented 4 years ago

If I am using cucumber version 5.5.0 the plugin @CucumberOptions is working fine with target\cucumber\cucumberReport.json , it is creating cucumber directory successfully but when I update the version to 6.6.0 it starts giving me the same error.

So I simply mention json:target/cucumberReport.json plugin option where I removed the cucumber folder then it won't give you such error.

But why it is giving such error to not allowing to create a new directory inside the target folder by just upgrading the version, don't know.

aslakhellesoy commented 4 years ago

That's useful information @ITMANUPAM - hopefully it will make it easier to identify the problem. We did touch this code recently, so I would not be surprised if a regression snuck in!

mpkorstanje commented 4 years ago

I can't reproduce this on Windows 7. If someone can debug PluginFactory.createFileOutputStream and see what is going on that would be most helpful.

However I'm guessing that mkdirs fails for some reason. Commenting it out is the only way I can reproduce the exception. I've added a test for the case and replaced mkdirs with Files.createDirectories which checks for a few more edge cases. But without confirmation its a stab in the dark.

mpkorstanje commented 4 years ago

@aslakhellesoy @ITMANUPAM with v6.6.1 released, could you please try and see if that resolved the issue.

mpkorstanje commented 4 years ago

Closing this due to a lack of feedback. Feel free to reopen with more information.

mvalle commented 4 years ago

I'm still facing this issue with 6.7.0

mpkorstanje commented 4 years ago

@mvalle I'm afraid that doesn't help much.

Could you debug PluginFactory.createFileOutputStream and see what is going wrong?

davidghiurco commented 4 years ago

Still facing this with all 6.x.x releases. Currently trying 6.8.1, and it's the same as when I tried it on 6.0.0.

@mpkorstanje I have debugged:

1) initializationError(Runner)
java.lang.IllegalArgumentException: Couldn't create parent directories of target/report/cucumber.json.
Make sure the the directory isn't a file.
The details are in the stack trace below:
        at io.cucumber.core.plugin.PluginFactory.createFileOutputStream(PluginFactory.java:220)
        at io.cucumber.core.plugin.PluginFactory.openStream(PluginFactory.java:198)
        at io.cucumber.core.plugin.PluginFactory.convert(PluginFactory.java:165)
        at io.cucumber.core.plugin.PluginFactory.instantiate(PluginFactory.java:98)
        at io.cucumber.core.plugin.PluginFactory.create(PluginFactory.java:63)
        at io.cucumber.core.plugin.Plugins.createPlugins(Plugins.java:32)
        at io.cucumber.core.plugin.Plugins.<init>(Plugins.java:25)
        at io.cucumber.junit.Cucumber.<init>(Cucumber.java:162)
        ... 20 trimmed

The root cause is the same as the in the original post, but I've seen various different error messages throughout trying various 6.x.x releases

Here's the reference code:

package ..;

import org.junit.BeforeClass;
import org.junit.runner.JUnitCore;
import org.junit.runner.RunWith;

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)
@CucumberOptions(
    plugin = {"pretty", "html:target/cucumber", "json:target/report/cucumber.json", "junit:target/report/cucumber.xml"},
    tags = "not @Ignore",
    features = "classpath:com/package/test")
public final class ProductAcceptanceTests {

    public static void main(String[] args) {
        JUnitCore.main(ProductAcceptanceTests.class.getName());
    }
}

In 5.x.x, the report is (correctly, and expectedly) generated target/report/ (report is a DIRECTORY) and it contains the following files: cucumber.json cucumber.xml

while target/cucumber contains formatter.js index.html jquery-3.4.1.min.js report.js style.css

In 6.x.x (and specifically 6.8.1): the generated file is target/cucumber (where cucumber is a FILE, not a DIRECTORY) This is apparently caused by a lack of "/" at the end of "html:target/cucumber". Note that in Cucumber-jvm 5.x.x, there was no such sensibility, and IMHO this sensibility shouldn't exist for the html plugin because it obviously needs to build a directory, not a file, so a lack of slash shouldn't confuse it.

Using

    plugin = {"pretty", "html:target/cucumber/", "json:target/report/cucumber.json", "junit:target/report/cucumber.xml"},

appears to solve the problem

BUT THERE'S ANOTHER PROBLEM (to which I have not found a solution). As I soon as I change the directory of the html report to be the same where the junit and json reports are:

    plugin = {"pretty", "html:target/report/", "json:target/report/cucumber.json", "junit:target/report/cucumber.xml"},

I get the original poster's problem:

java.lang.IllegalArgumentException: Couldn't create parent directories of target/report/cucumber.json. Make sure the the directory isn't a file. The details are in the stack trace below: at io.cucumber.core.plugin.PluginFactory.createFileOutputStream(PluginFactory.java:220) at io.cucumber.core.plugin.PluginFactory.openStream(PluginFactory.java:198) at io.cucumber.core.plugin.PluginFactory.convert(PluginFactory.java:165) at io.cucumber.core.plugin.PluginFactory.instantiate(PluginFactory.java:98) at io.cucumber.core.plugin.PluginFactory.create(PluginFactory.java:63) at io.cucumber.core.plugin.Plugins.createPlugins(Plugins.java:32) at io.cucumber.core.plugin.Plugins.(Plugins.java:25)

Caused by: java.nio.file.FileAlreadyExistsException: /target/report at sun.nio.fs.UnixException.translateToIOException(UnixException.java:88) at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) at sun.nio.fs.UnixFileSystemProvider.createDirectory(UnixFileSystemProvider.java:384) at java.nio.file.Files.createDirectory(Files.java:674) at java.nio.file.Files.createAndCheckIsDirectory(Files.java:781) at java.nio.file.Files.createDirectories(Files.java:727) at io.cucumber.core.plugin.PluginFactory.createFileOutputStream(PluginFactory.java:217) ... 28 more

So I have to stick with cucumber-jvm 5.7.0 for now.

mpkorstanje commented 4 years ago

Can you try: html:target/report/cucumber.html. Note the extension.

davidghiurco commented 4 years ago

yup! cucumber-jvm:6.8.1 works the above suggestion^

Now I have:

plugin = {"pretty", "html:target/report/cucumber.html", "json:target/report/cucumber.json", "junit:target/report/cucumber.xml"},

I no longer get java.lang.IllegalArgumentException: Couldn't create parent directories of target/report/cucumber.json.

It seems from 5.x.x -> 6.x.x the change for HTML report is that the html plugin now creates a single .html file, instead of 5 files (formatter.js index.html jquery-3.4.1.min.js report.js style.css). Thus it expects to create a file, not a directory with files anymore. This seems to have been the root cause of my problems with the other reports.

mpkorstanje commented 4 years ago

That's in the change log. But note that it's not the same problem as the @ITMANUPAM mentioned which involves the json plugin.

https://github.com/cucumber/cucumber-jvm/blob/main/release-notes/v6.0.0.md#improved-html-formatter

mpkorstanje commented 4 years ago

Mmh. I guess they could be related if the html plugin goed first. Shows the importance of creating an MCVE.

epragtbeamtree commented 3 years ago

Hi @mpkorstanje , after upgrading Cucumber from 6.3.0 to 6.11.0, I'm facing the same issue. I'm on Mac, but our build machines are Windows machines, and both have the same issue. I can reliably reproduce it:

image

I've checked the file it's trying to read (/Users/erikpragt/projects/pks/rippledown/userneeds/build/run/build/test-results/junit.xml), but that file doesn't exist (yet).

This is coming from our Cucumber arguments:

val argsForCuke = listOf("--strict",
                   "--plugin", "junit:build/test-results/junit.xml",
                   "--plugin", "html:build/test-results-html/cucumber.html",
                   "--tags", "@${requirement}",
                   "--glue", "steps", "/${projectDir.path}/src/test/resources/gherkin")

If I change the above to this:

val argsForCuke = listOf("--strict",
                   "--plugin", "junit:build/junit.xml",
                   "--plugin", "html:build/cucumber.html",
                   "--tags", "@${requirement}",
                   "--glue", "steps", "/${projectDir.path}/src/test/resources/gherkin")

Then the test passes, so it seems that something doesn't work well with directory creations.

I did some more debugging, and at that line, if I do:

file.getAbsoluteFile()         // /Users/erikpragt/projects/pks/rippledown/userneeds/build/run/build/test-results/junit.xml
file.getParentFile()             // build/test-results
file.getParentFile().isFile() // the result is true
file.getParentFile().isDirectory() // the result is false

So, it seems that something creates this test-results part as a file, instead of a directory.

However, when I check the directory using my terminal, I see this:

➜  build git:(PRD-4095_Upgrade_Cucumber_6.3_to_latest_version) ✗ ls -al
total 0
drwxr-xr-x   3 erikpragt  staff   96  6 Oct 11:20 .
drwxr-xr-x  15 erikpragt  staff  480  6 Oct 11:12 ..
drwxr-xr-x   2 erikpragt  staff   64  6 Oct 11:20 test-results
➜  build git:(PRD-4095_Upgrade_Cucumber_6.3_to_latest_version) ✗ pwd
/Users/erikpragt/projects/pks/rippledown/userneeds/build/run/build

So, it seems test-results is directory after all?

Anything I can do to help debugging this issue?

violplayer commented 2 years ago

I could reproduce this issue with Windows 10 Enterprise, Oracle JDK1.8.0_301, cucumber 6.8.1 and 6.11.0

When debugging the code of io.cucumber.core.plugin.PluginFactory#createFileOutputStream() as described I had these findings:

Seems that this issue could be easily fixed by using FileOutputStream(canonicalFile) in line 237.

mpkorstanje commented 2 years ago

canonicalfile and file are pointing to the same location

What was the difference between the two?

violplayer commented 2 years ago

file = "target/cucumber-reports/cucumber.html" (as in @CucumberOptions) canonicalFile = "c:/users/violplayer/projects/myProject/target/cucumber-reports/cucumber.html"

The maven project folder being the current directory, they describe the same file.

mpkorstanje commented 2 years ago

That's odd.

Seems that this issue could be easily fixed by using FileOutputStream(canonicalFile) in line 237.

I don't have a windows system available to test this with. Would you be able to make this change and test it yourself?

violplayer commented 2 years ago

The exception is thrown in some native code. I'll do the change, test it and create a pull request.

stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in two months if no further activity occurs.