Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confusing/misleading error creating FileOutputStream for plugin #2108

Open
aslakhellesoy opened this issue Sep 3, 2020 · 20 comments · Fixed by #2168
Open

Confusing/misleading error creating FileOutputStream for plugin #2108

aslakhellesoy opened this issue Sep 3, 2020 · 20 comments · Fixed by #2168

Comments

@aslakhellesoy
Copy link
Contributor

aslakhellesoy commented Sep 3, 2020

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.

@aslakhellesoy aslakhellesoy added the 🐛 bug Defect / Bug label Sep 3, 2020
@mpkorstanje
Copy link
Contributor

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
Copy link

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
Copy link
Contributor Author

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 added a commit that referenced this issue Sep 3, 2020
Replaced `TempDir` utility with `@TempDir` from JUnit 5 and Javas path API.

See: #2108
mpkorstanje added a commit that referenced this issue Sep 3, 2020
…tFile)

While `makeDirs` makes an attempt at creating the directories
`createDirectories` will throw an exception when the directories could not be
created or if the file exists but was not a directory.

See: #2108
@mpkorstanje
Copy link
Contributor

mpkorstanje commented Sep 3, 2020

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 mpkorstanje added 🙏 help wanted Help wanted - not prioritized by core team platform: windows labels Sep 3, 2020
mpkorstanje added a commit that referenced this issue Sep 4, 2020
Replaced `TempDir` utility with `@TempDir` from JUnit 5 and Javas path API.

See: #2108
mpkorstanje added a commit that referenced this issue Sep 4, 2020
…tFile)

While `makeDirs` makes an attempt at creating the directories
`createDirectories` will throw an exception when the directories could not be
created or if the file exists but was not a directory.

See: #2108
@mpkorstanje
Copy link
Contributor

mpkorstanje commented Sep 8, 2020

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

@mpkorstanje
Copy link
Contributor

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

@mvalle
Copy link

mvalle commented Sep 18, 2020

I'm still facing this issue with 6.7.0

@mpkorstanje
Copy link
Contributor

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

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

@davidghiurco
Copy link

davidghiurco commented Oct 16, 2020

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
Copy link
Contributor

mpkorstanje commented Oct 16, 2020

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

@davidghiurco
Copy link

davidghiurco commented Oct 16, 2020

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
Copy link
Contributor

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
Copy link
Contributor

mpkorstanje commented Oct 16, 2020

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

@mpkorstanje mpkorstanje reopened this Oct 16, 2020
@mpkorstanje mpkorstanje removed status: waiting-for-feedback 🙏 help wanted Help wanted - not prioritized by core team labels Oct 19, 2020
mpkorstanje added a commit that referenced this issue Oct 31, 2020
Cucumber v6 introduced a new html formatter. This formatter writes to a file
rather then a directory[1]. However a typical use to ensure all files and up
in a single folder would be `plugin=json:target/cucumber/report.json, html:target/cucumber`.

So depending on the order this results in Cucumber being unable to either
create the `target/cucumber` as file for the html report or `target/cucumber`
as a directory for the json report.

The improved error message should point this out.

Fixes: #2108

Additionally the PluginFactory tests were rewritten to make them follow the
arrange-act-test pattern.

1. https://github.com/cucumber/cucumber-jvm/blob/main/release-notes/v6.0.0.md#improved-html-formatter
mpkorstanje added a commit that referenced this issue Oct 31, 2020
Cucumber v6 introduced a new html formatter. This formatter writes to a file
rather then a directory[1]. However a typical use to ensure all files and up
in a single folder would be `plugin=json:target/cucumber/report.json, html:target/cucumber`.

So depending on the order this results in Cucumber being unable to either
create the `target/cucumber` as file for the html report or `target/cucumber`
as a directory for the json report.

The improved error message should point this out.

Fixes: #2108

Additionally the PluginFactory tests were rewritten to make them follow the
arrange-act-test pattern.

1. https://github.com/cucumber/cucumber-jvm/blob/main/release-notes/v6.0.0.md#improved-html-formatter
mpkorstanje added a commit that referenced this issue Oct 31, 2020
Cucumber v6 introduced a new html formatter. This formatter writes to a file
rather then a directory[1]. However a typical use to ensure all files and up
in a single folder would be `plugin=json:target/cucumber/report.json, html:target/cucumber`.

So depending on the order this results in Cucumber being unable to either
create the `target/cucumber` as file for the html report or `target/cucumber`
as a directory for the json report.

The improved error message should point this out.

Fixes: #2108

Additionally the PluginFactory tests were rewritten to make them follow the
arrange-act-test pattern.

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

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
Copy link
Contributor

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:

  • canonicalfile and file are pointing to the same location
  • file.getParentFile().exists() returns false
  • file.getParentFile().getCanonicalFile().exists() returns true
  • canonicalFile.getParentFile().exists() returns true
  • new FileOutputStream(file) throws IOException
  • new FileOutputStream(canonicalFile) does not throw IOException and works as it should

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

@mpkorstanje
Copy link
Contributor

canonicalfile and file are pointing to the same location

What was the difference between the two?

@mpkorstanje mpkorstanje reopened this Feb 10, 2022
@violplayer
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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

violplayer pushed a commit to violplayer/cucumber-jvm that referenced this issue Feb 10, 2022
violplayer pushed a commit to violplayer/cucumber-jvm that referenced this issue Feb 10, 2022
violplayer pushed a commit to violplayer/cucumber-jvm that referenced this issue Feb 10, 2022
@stale
Copy link

stale bot commented Apr 14, 2023

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.

@stale stale bot added the ⌛ stale Will soon be closed by stalebot unless there is activity label Apr 14, 2023
@mpkorstanje mpkorstanje removed the ⌛ stale Will soon be closed by stalebot unless there is activity label Apr 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants