Cucumber-jvm: Confusing/misleading error creating FileOutputStream for plugin

Created on 3 Sep 2020  路  13Comments  路  Source: cucumber/cucumber-jvm

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
  • Cucumber-JVM 6.6.0
  • JDK 1.8.0_261
  • Windows

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

  • The path is a directory (unlikely)
  • We're not doing mkdir -p
  • Something to do with backslashes?
  • File permissions (unlikely on windows?)

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

bug windows

Most helpful comment

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

All 13 comments

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

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.

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!

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.

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

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

I'm still facing this issue with 6.7.0

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

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

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.

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

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.

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

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

Was this page helpful?
0 / 5 - 0 ratings