You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2026/10/16 17:02:07 UTC
(grails-core) 03/06: Test File values for null instead of truth in the file system interaction
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 81e4dc83731d28dd05ed56ac5e5b7106fdc80024
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Fri Oct 16 12:55:58 2026 -0400
Test File values for null instead of truth in the file system interaction
Groovy 5 and later evaluate a java.io.File that does not exist as false,
where Groovy 4 treated every File as true. The guards in both copies of
FileSystemInteractionImpl relied on the old meaning, so on Grails 8 and later
projectPath returned an empty string for a file that is not there yet, which
is exactly the state a template destination is in after a failed render has
deleted it: the TemplateRendererImpl error message then read "Error rendering
template to destination : ..." with no path. The same guards decided the base
directory in file(), the copy spec destination and the empty-path check in
resource(). Each one now tests for null (or an empty path) explicitly.
Covered by a feature on the shell-cli FileSystemInteractionImplSpec and a new
spec for the grails.dev.commands.io copy, both using files that do not exist.
---
.../commands/io/FileSystemInteractionImpl.groovy | 9 +--
.../io/FileSystemInteractionImplSpec.groovy | 75 ++++++++++++++++++++++
.../commands/io/FileSystemInteractionImpl.groovy | 9 +--
.../io/FileSystemInteractionImplSpec.groovy | 22 +++++++
4 files changed, 107 insertions(+), 8 deletions(-)
diff --git a/grails-core/src/main/groovy/grails/dev/commands/io/FileSystemInteractionImpl.groovy b/grails-core/src/main/groovy/grails/dev/commands/io/FileSystemInteractionImpl.groovy
index f57e4ffa81..0f37ff555d 100644
--- a/grails-core/src/main/groovy/grails/dev/commands/io/FileSystemInteractionImpl.groovy
+++ b/grails-core/src/main/groovy/grails/dev/commands/io/FileSystemInteractionImpl.groovy
@@ -85,7 +85,7 @@ class FileSystemInteractionImpl implements FileSystemInteraction {
FileSystemInteraction.CopySpec spec = new FileSystemInteraction.CopySpec()
callable.delegate = spec
callable.call()
- if (spec.from && spec.into) {
+ if (spec.from && spec.into != null) {
if (spec.from instanceof Iterable) {
copyAll((Iterable) spec.from, spec.into)
}
@@ -158,7 +158,7 @@ class FileSystemInteractionImpl implements FileSystemInteraction {
if (path instanceof File) return (File) path
else if (path instanceof Resource) return ((Resource) path).file
else {
- new File(baseDir ?: new File('.'), path.toString())
+ new File(baseDir != null ? baseDir : new File('.'), path.toString())
}
}
@@ -213,7 +213,7 @@ class FileSystemInteractionImpl implements FileSystemInteraction {
*/
@Override
Resource resource(Object path) {
- if (!path) return null
+ if (path == null || path.toString().isEmpty()) return null
if (path instanceof Resource) return (Resource) path
def f = file(path)
if (f?.exists() && f.isFile()) {
@@ -261,7 +261,8 @@ class FileSystemInteractionImpl implements FileSystemInteraction {
@Override
String projectPath(Object path) {
def file = file(path)
- if (file) {
+ // a File that does not exist is false on Groovy 5 and later, so test for null explicitly
+ if (file != null) {
def basePath = baseDir.canonicalPath
return (file.canonicalPath - basePath).substring(1)
}
diff --git a/grails-core/src/test/groovy/grails/dev/commands/io/FileSystemInteractionImplSpec.groovy b/grails-core/src/test/groovy/grails/dev/commands/io/FileSystemInteractionImplSpec.groovy
new file mode 100644
index 0000000000..ddd22a28f0
--- /dev/null
+++ b/grails-core/src/test/groovy/grails/dev/commands/io/FileSystemInteractionImplSpec.groovy
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package grails.dev.commands.io
+
+import org.grails.io.support.FileSystemResource
+import spock.lang.Specification
+import spock.lang.TempDir
+
+class FileSystemInteractionImplSpec extends Specification {
+
+ @TempDir
+ File directory
+
+ FileSystemInteractionImpl interaction
+
+ void setup() {
+ interaction = new FileSystemInteractionImpl(directory)
+ }
+
+ def 'files and resources are resolved relative to the project and existing objects are preserved'() {
+ given:
+ def file = new File(directory, 'example.txt')
+ file.text = 'example'
+ def resource = new FileSystemResource(file)
+
+ expect:
+ interaction.file('example.txt') == file
+ interaction.file(file).is(file)
+ interaction.file(resource) == file
+ interaction.resource(resource).is(resource)
+ interaction.resource('example.txt').file == file
+ interaction.resource(null) == null
+ interaction.resource('') == null
+ !interaction.resource('missing.txt').exists()
+ interaction.projectPath(file) == 'example.txt'
+ }
+
+ def 'files that do not exist yet are resolved like existing ones'() {
+ given: 'a project directory that has not been created and files below it'
+ def pending = new File(directory, 'pending')
+ def pendingInteraction = new FileSystemInteractionImpl(pending)
+ def missing = new File(directory, 'nested/missing.txt')
+ def existing = interaction.file('present.txt')
+ existing.text = 'present'
+
+ when:
+ interaction.copy {
+ from(existing)
+ into(new File(directory, 'target'))
+ }
+
+ then:
+ interaction.projectPath(missing) == 'nested/missing.txt'
+ interaction.projectPath('missing.txt') == 'missing.txt'
+ !interaction.resource(missing).exists()
+ pendingInteraction.file('build.gradle') == new File(pending, 'build.gradle')
+ interaction.file('target/present.txt').text == 'present'
+ }
+}
diff --git a/grails-shell-cli/src/main/groovy/org/grails/cli/profile/commands/io/FileSystemInteractionImpl.groovy b/grails-shell-cli/src/main/groovy/org/grails/cli/profile/commands/io/FileSystemInteractionImpl.groovy
index 6dbd0aadb2..53f8b5f6b0 100644
--- a/grails-shell-cli/src/main/groovy/org/grails/cli/profile/commands/io/FileSystemInteractionImpl.groovy
+++ b/grails-shell-cli/src/main/groovy/org/grails/cli/profile/commands/io/FileSystemInteractionImpl.groovy
@@ -87,7 +87,7 @@ class FileSystemInteractionImpl implements FileSystemInteraction {
FileSystemInteraction.CopySpec spec = new FileSystemInteraction.CopySpec()
callable.delegate = spec
callable.call()
- if (spec.from && spec.into) {
+ if (spec.from && spec.into != null) {
if (spec.from instanceof Iterable) {
copyAll((Iterable) spec.from, spec.into)
}
@@ -161,7 +161,7 @@ class FileSystemInteractionImpl implements FileSystemInteraction {
else if (path instanceof Resource) return ((Resource) path).file
else {
def baseDir = executionContext.baseDir
- new File(baseDir ?: new File('.'), path.toString())
+ new File(baseDir != null ? baseDir : new File('.'), path.toString())
}
}
@@ -216,7 +216,7 @@ class FileSystemInteractionImpl implements FileSystemInteraction {
*/
@Override
Resource resource(Object path) {
- if (!path) return null
+ if (path == null || path.toString().isEmpty()) return null
if (path instanceof Resource) return (Resource) path
def f = file(path)
if (f?.exists() && f.isFile()) {
@@ -264,7 +264,8 @@ class FileSystemInteractionImpl implements FileSystemInteraction {
@Override
String projectPath(Object path) {
def file = file(path)
- if (file) {
+ // a File that does not exist is false on Groovy 5 and later, so test for null explicitly
+ if (file != null) {
def basePath = executionContext.baseDir.canonicalPath
return (file.canonicalPath - basePath).substring(1)
}
diff --git a/grails-shell-cli/src/test/groovy/org/grails/cli/profile/commands/io/FileSystemInteractionImplSpec.groovy b/grails-shell-cli/src/test/groovy/org/grails/cli/profile/commands/io/FileSystemInteractionImplSpec.groovy
index 953b976bee..c7acbb161d 100644
--- a/grails-shell-cli/src/test/groovy/org/grails/cli/profile/commands/io/FileSystemInteractionImplSpec.groovy
+++ b/grails-shell-cli/src/test/groovy/org/grails/cli/profile/commands/io/FileSystemInteractionImplSpec.groovy
@@ -65,6 +65,28 @@ class FileSystemInteractionImplSpec extends Specification {
interaction.projectPath(file) == 'example.txt'
}
+ def 'files that do not exist yet are resolved like existing ones'() {
+ given: 'a project directory that has not been created and files below it'
+ def pending = new File(directory, 'pending')
+ def pendingInteraction = new FileSystemInteractionImpl(Stub(ExecutionContext) { getBaseDir() >> pending })
+ def missing = new File(directory, 'nested/missing.txt')
+ def existing = interaction.file('present.txt')
+ existing.text = 'present'
+
+ when:
+ interaction.copy {
+ from(existing)
+ into(new File(directory, 'target'))
+ }
+
+ then:
+ interaction.projectPath(missing) == 'nested/missing.txt'
+ interaction.projectPath('missing.txt') == 'missing.txt'
+ !interaction.resource(missing).exists()
+ pendingInteraction.file('build.gradle') == new File(pending, 'build.gradle')
+ interaction.file('target/present.txt').text == 'present'
+ }
+
def 'mkdir and delete operate on project files and return the interaction for chaining'() {
when:
def result = interaction.mkdir('nested/child')