This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-js-provider.git
The following commit(s) were added to refs/heads/master by this push:
new 27356b3 SLING-12635 - JS Use Scripts should only be read on-demand
27356b3 is described below
commit 27356b32ef40cd2f466a1a7fb275f259c3dee510
Author: Radu Cotescu <[email protected]>
AuthorDate: Mon Jan 27 14:18:16 2025 +0100
SLING-12635 - JS Use Scripts should only be read on-demand
* added a test to show that the stream is already read when the
DependencyResolver solves the ScriptNameAwareReader
* added a test to show that the stream is already read when the
SlyBindingsValuesProvider initialises the ScriptNameAwareReader;
the reader needs to be read only when the JsEnvironment needs to
compile/evaluate a script
* read all scripts lazily, only when they really need to be loaded/reloaded
---
pom.xml | 26 +++-
.../scripting/sightly/js/impl/JsEnvironment.java | 13 +-
.../js/impl/jsapi/SlyBindingsValuesProvider.java | 61 +++++----
.../sightly/js/impl/use/DependencyResolver.java | 105 +++++++---------
.../impl/jsapi/SlyBindingsValuesProviderTest.java | 93 ++++++++++++++
.../js/impl/use/DependencyResolverTest.java | 138 +++++++++++++++++++++
6 files changed, 336 insertions(+), 100 deletions(-)
diff --git a/pom.xml b/pom.xml
index 4eab4b6..1e3244e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -134,12 +134,6 @@
<version>3.5</version>
<scope>provided</scope>
</dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <version>2.4</version>
- <scope>provided</scope>
- </dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
@@ -154,6 +148,26 @@
<version>1.7.7.1_1</version>
<scope>provided</scope>
</dependency>
+
+ <!-- JUnit -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <version>5.11.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>5.11.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <version>5.11.0</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git
a/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java
b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java
index 8e36e8a..577d603 100644
---
a/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java
+++
b/src/main/java/org/apache/sling/scripting/sightly/js/impl/JsEnvironment.java
@@ -18,6 +18,8 @@
******************************************************************************/
package org.apache.sling.scripting.sightly.js.impl;
+import java.io.Closeable;
+
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.ScriptContext;
@@ -25,7 +27,6 @@ import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
-import org.apache.commons.io.IOUtils;
import org.apache.sling.api.scripting.LazyBindings;
import org.apache.sling.scripting.core.ScriptNameAwareReader;
import org.apache.sling.scripting.sightly.SightlyException;
@@ -129,10 +130,18 @@ public class JsEnvironment {
} catch (ScriptException e) {
throw new SightlyException(e);
} finally {
- IOUtils.closeQuietly(reader);
+ closeQuietly(reader);
}
});
}
+ private void closeQuietly(Closeable closeable) {
+ try {
+ closeable.close();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
}
diff --git
a/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java
b/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java
index ba1314d..8bb98d0 100644
---
a/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java
+++
b/src/main/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProvider.java
@@ -21,7 +21,6 @@ package org.apache.sling.scripting.sightly.js.impl.jsapi;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
@@ -32,7 +31,6 @@ import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.servlet.http.HttpServletRequest;
-import org.apache.commons.io.IOUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.scripting.LazyBindings;
@@ -90,13 +88,13 @@ public class SlyBindingsValuesProvider {
" dependency chain."
)
- String[] org_apache_sling_scripting_sightly_js_bindings() default
"sightly:" + SlyBindingsValuesProvider.SLING_NS_PATH;
+ String[] org_apache_sling_scripting_sightly_js_bindings() default
SlyBindingsValuesProvider.SLING_NS_PATH;
}
public static final String SCR_PROP_JS_BINDING_IMPLEMENTATIONS =
"org.apache.sling.scripting.sightly.js.bindings";
- public static final String SLING_NS_PATH =
"/libs/sling/sightly/js/internal/sly.js";
+ public static final String SLING_NS_PATH =
"sightly:/libs/sling/sightly/js/internal/sly.js";
public static final String Q_PATH =
"/libs/sling/sightly/js/3rd-party/q.js";
private static final String REQ_NS =
SlyBindingsValuesProvider.class.getCanonicalName();
@@ -145,12 +143,12 @@ public class SlyBindingsValuesProvider {
@Activate
protected void activate(Configuration configuration) {
- String[] factories = PropertiesUtil.toStringArray(
+ String[] configuredFactories = PropertiesUtil.toStringArray(
configuration.org_apache_sling_scripting_sightly_js_bindings(),
new String[]{SLING_NS_PATH}
);
- scriptPaths = new LinkedHashMap<>(factories.length);
- for (String f : factories) {
+ scriptPaths = new LinkedHashMap<>(configuredFactories.length);
+ for (String f : configuredFactories) {
String[] parts = f.split(":");
if (parts.length == 2) {
scriptPaths.put(parts[0], parts[1]);
@@ -197,24 +195,24 @@ public class SlyBindingsValuesProvider {
if (resource == null) {
throw new SightlyException("Sly namespace loader could not find
the following script: " + path);
}
- try {
- AsyncContainer container =
- jsEnvironment.runScript(
- new ScriptNameAwareReader(
- new
StringReader(IOUtils.toString(resource.adaptTo(InputStream.class),
StandardCharsets.UTF_8)),
- resource.getPath()
- ),
- createBindings(bindings, resource.getPath()),
- new LazyBindings()
- );
- Object obj = container.getResult();
- if (!(obj instanceof Function)) {
- throw new SightlyException("Script " + path + " was expected
to return a function.");
- }
- return (Function) obj;
- } catch (IOException e) {
- throw new SightlyException("Cannot read script " + path + " .");
+ InputStream inputStream = resource.adaptTo(InputStream.class);
+ if (inputStream == null) {
+ throw new SightlyException("Sly namespace loader could not read
the following script: " + path);
}
+ AsyncContainer container =
+ jsEnvironment.runScript(
+ new ScriptNameAwareReader(
+ new InputStreamReader(inputStream,
StandardCharsets.UTF_8),
+ resource.getPath()
+ ),
+ createBindings(bindings, resource.getPath()),
+ new LazyBindings()
+ );
+ Object obj = container.getResult();
+ if (!(obj instanceof Function)) {
+ throw new SightlyException("Script " + path + " was expected to
return a function.");
+ }
+ return (Function) obj;
}
private Bindings createBindings(Bindings global, String factoryPath) {
@@ -268,14 +266,12 @@ public class SlyBindingsValuesProvider {
Context context = Context.enter();
context.initStandardObjects();
context.setOptimizationLevel(9);
- InputStream reader = null;
- try {
- Resource resource = resolver.getResource(Q_PATH);
- if (resource == null) {
- LOGGER.warn("Could not load Q library at path: " + Q_PATH);
- return null;
- }
- reader = resource.adaptTo(InputStream.class);
+ Resource resource = resolver.getResource(Q_PATH);
+ if (resource == null) {
+ LOGGER.warn("Could not load Q library at path: " + Q_PATH);
+ return null;
+ }
+ try (InputStream reader = resource.adaptTo(InputStream.class)) {
if (reader == null) {
LOGGER.warn("Could not read content of Q library");
return null;
@@ -285,7 +281,6 @@ public class SlyBindingsValuesProvider {
LOGGER.error("Unable to compile the Q library at path " + Q_PATH +
".", e);
} finally {
Context.exit();
- IOUtils.closeQuietly(reader);
}
return null;
}
diff --git
a/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java
b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java
index bfca8c8..ed3bd66 100644
---
a/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java
+++
b/src/main/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolver.java
@@ -19,15 +19,13 @@
package org.apache.sling.scripting.sightly.js.impl.use;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.nio.charset.StandardCharsets;
-
import javax.script.Bindings;
import javax.script.ScriptEngine;
-import org.apache.commons.io.IOUtils;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
@@ -56,71 +54,60 @@ public class DependencyResolver {
if (!Utils.isJsScript(dependency)) {
throw new SightlyException("Only JS scripts are allowed as
dependencies. Invalid dependency: " + dependency);
}
- ScriptNameAwareReader reader = null;
- IOException ioException = null;
- try {
- // attempt to retrieve the dependency directly (as an absolute
path or relative to the search paths)
- Resource scriptResource =
scriptingResourceResolver.getResource(dependency);
- Resource caller = getCaller(bindings);
- if (caller != null) {
- Resource callerType = caller.getParent();
- if (scriptResource == null && callerType != null) {
- SlingHttpServletRequest request =
(SlingHttpServletRequest) bindings.get(SlingBindings.REQUEST);
- String driverType =
request.getResource().getResourceType();
- Resource driver =
scriptingResourceResolver.getResource(driverType);
- if (driver != null) {
- Resource hierarchyResource =
getHierarchyResource(callerType, driver);
- while (hierarchyResource != null && scriptResource ==
null) {
- if (dependency.startsWith("..")) {
- // relative path
- String absolutePath =
ResourceUtil.normalize(hierarchyResource.getPath() + "/" + dependency);
- if (StringUtils.isNotEmpty(absolutePath)) {
- scriptResource =
scriptingResourceResolver.getResource(absolutePath);
- }
- } else {
- scriptResource =
hierarchyResource.getChild(dependency);
- }
- if (scriptResource == null) {
- String nextType =
hierarchyResource.getResourceSuperType();
- if (nextType != null) {
- hierarchyResource =
scriptingResourceResolver.getResource(nextType);
- } else {
- hierarchyResource = null;
- }
- }
- }
- }
- // cannot find a dependency relative to the resource type;
locate it solely based on the caller
- if (scriptResource == null) {
+ // attempt to retrieve the dependency directly (as an absolute path or
relative to the search paths)
+ Resource scriptResource =
scriptingResourceResolver.getResource(dependency);
+ Resource caller = getCaller(bindings);
+ if (caller != null) {
+ Resource callerType = caller.getParent();
+ if (scriptResource == null && callerType != null) {
+ SlingHttpServletRequest request = (SlingHttpServletRequest)
bindings.get(SlingBindings.REQUEST);
+ String driverType = request.getResource().getResourceType();
+ Resource driver =
scriptingResourceResolver.getResource(driverType);
+ if (driver != null) {
+ Resource hierarchyResource =
getHierarchyResource(callerType, driver);
+ while (hierarchyResource != null && scriptResource ==
null) {
if (dependency.startsWith("..")) {
// relative path
- String absolutePath =
ResourceUtil.normalize(callerType.getPath() + "/" + dependency);
+ String absolutePath =
ResourceUtil.normalize(hierarchyResource.getPath() + "/" + dependency);
if (StringUtils.isNotEmpty(absolutePath)) {
scriptResource =
scriptingResourceResolver.getResource(absolutePath);
}
} else {
- scriptResource = callerType.getChild(dependency);
+ scriptResource =
hierarchyResource.getChild(dependency);
+ }
+ if (scriptResource == null) {
+ String nextType =
hierarchyResource.getResourceSuperType();
+ if (nextType != null) {
+ hierarchyResource =
scriptingResourceResolver.getResource(nextType);
+ } else {
+ hierarchyResource = null;
+ }
}
}
}
+ // cannot find a dependency relative to the resource type;
locate it solely based on the caller
+ if (scriptResource == null) {
+ if (dependency.startsWith("..")) {
+ // relative path
+ String absolutePath =
ResourceUtil.normalize(callerType.getPath() + "/" + dependency);
+ if (StringUtils.isNotEmpty(absolutePath)) {
+ scriptResource =
scriptingResourceResolver.getResource(absolutePath);
+ }
+ } else {
+ scriptResource = callerType.getChild(dependency);
+ }
+ }
}
- if (scriptResource == null) {
- throw new SightlyException(String.format("Unable to load
script dependency %s.", dependency));
- }
- InputStream scriptStream =
scriptResource.adaptTo(InputStream.class);
- if (scriptStream == null) {
- throw new SightlyException(String.format("Unable to read
script %s.", dependency));
- }
- reader = new ScriptNameAwareReader(new
StringReader(IOUtils.toString(scriptStream, StandardCharsets.UTF_8)),
- scriptResource.getPath());
- IOUtils.closeQuietly(scriptStream);
- } catch (IOException e) {
- ioException = e;
}
- if (ioException != null) {
- throw new SightlyException(String.format("Unable to load script
dependency %s.", dependency), ioException);
+ if (scriptResource == null) {
+ throw new SightlyException(String.format("Unable to load script
dependency %s.", dependency));
+ }
+ InputStream scriptStream = scriptResource.adaptTo(InputStream.class);
+ if (scriptStream == null) {
+ throw new SightlyException(String.format("Unable to read script
%s.", dependency));
}
- return reader;
+ return new ScriptNameAwareReader(new InputStreamReader(scriptStream,
StandardCharsets.UTF_8),
+ scriptResource.getPath());
}
private Resource getCaller(Bindings bindings) {
diff --git
a/src/test/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProviderTest.java
b/src/test/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProviderTest.java
new file mode 100644
index 0000000..974b5b6
--- /dev/null
+++
b/src/test/java/org/apache/sling/scripting/sightly/js/impl/jsapi/SlyBindingsValuesProviderTest.java
@@ -0,0 +1,93 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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
+ ~
+ ~ http://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 org.apache.sling.scripting.sightly.js.impl.jsapi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+
+import javax.script.Bindings;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.scripting.core.ScriptNameAwareReader;
+import org.apache.sling.scripting.sightly.js.impl.JsEnvironment;
+import org.apache.sling.scripting.sightly.js.impl.async.AsyncContainer;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mozilla.javascript.Function;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class SlyBindingsValuesProviderTest {
+
+ @Mock
+ private ResourceResolver resolver;
+
+ @Mock
+ private Resource scriptResource;
+
+ @Mock
+ private SlyBindingsValuesProvider.Configuration configuration;
+
+ @Mock
+ private JsEnvironment jsEnvironment;
+
+ @Mock
+ private AsyncContainer asyncContainer;
+
+ @Mock
+ private Function function;
+
+ private InputStream inputStream;
+
+ @BeforeEach
+ void setUp() {
+ String scriptPath =
SlyBindingsValuesProvider.SLING_NS_PATH.split(":")[1];
+ inputStream =
spy(Objects.requireNonNull(getClass().getResourceAsStream("/SLING-INF" +
scriptPath)));
+ when(scriptResource.getPath()).thenReturn(scriptPath);
+ when(resolver.getResource(scriptPath)).thenReturn(scriptResource);
+
when(scriptResource.adaptTo(InputStream.class)).thenReturn(inputStream);
+ when(asyncContainer.getResult()).thenReturn(function);
+ when(jsEnvironment.runScript(any(ScriptNameAwareReader.class),
any(Bindings.class), any(Bindings.class))).thenReturn(asyncContainer);
+ }
+
+ @Test
+ void testResourceLoading_streamNotRead() throws IOException {
+ assertNotNull(inputStream);
+ SlyBindingsValuesProvider provider = new SlyBindingsValuesProvider();
+ provider.activate(configuration);
+ provider.initialise(resolver, jsEnvironment, new SlingBindings());
+ verify(inputStream, never()).read();
+ verify(inputStream, never()).read(any(byte[].class));
+ verify(inputStream, never()).read(any(byte[].class), anyInt(),
anyInt());
+ }
+
+}
diff --git
a/src/test/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolverTest.java
b/src/test/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolverTest.java
new file mode 100644
index 0000000..3a2e060
--- /dev/null
+++
b/src/test/java/org/apache/sling/scripting/sightly/js/impl/use/DependencyResolverTest.java
@@ -0,0 +1,138 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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
+ ~
+ ~ http://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 org.apache.sling.scripting.sightly.js.impl.use;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.scripting.core.ScriptNameAwareReader;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class DependencyResolverTest {
+
+ private static final String CALLER_PATH = "/libs/caller/caller.html";
+ private static final String SCRIPT_PATH = "/libs/caller/caller.js";
+
+ @Mock
+ private ResourceResolver scriptingResourceResolver;
+
+ @Mock
+ private Resource caller;
+
+ @Mock
+ private Resource callerParent;
+
+ @Mock
+ private Resource dependency;
+
+ @Mock
+ private Resource content;
+
+ @Mock
+ private SlingHttpServletRequest request;
+
+ private DependencyResolver dependencyResolver;
+ private Bindings bindings;
+
+
+ @BeforeEach
+ void beforeEach() {
+ when(dependency.getPath()).thenReturn(SCRIPT_PATH);
+ when(caller.getParent()).thenReturn(callerParent);
+
when(scriptingResourceResolver.getResource(CALLER_PATH)).thenReturn(caller);
+
when(scriptingResourceResolver.getResource(SCRIPT_PATH)).thenReturn(dependency);
+ dependencyResolver = new DependencyResolver(scriptingResourceResolver);
+ bindings = new SlingBindings();
+ bindings.put(ScriptEngine.FILENAME, CALLER_PATH);
+ bindings.put(SlingBindings.REQUEST, request);
+ }
+
+ @Test
+ void testResourceLoading_streamNotRead() throws IOException {
+ InputStream stream = mock(InputStream.class);
+
+ // Configure the mock to return data and simulate EOF
+ byte[] mockData = "mocked content".getBytes();
+ AtomicInteger readCount = new AtomicInteger(0);
+
+ // Simulate reading data and EOF
+ when(stream.read(any(), anyInt(), anyInt())).thenAnswer(invocation -> {
+ int offset = invocation.getArgument(1);
+ int length = invocation.getArgument(2);
+
+ // Check if there's data left to read
+ if (readCount.get() >= mockData.length) {
+ return -1; // Simulate EOF
+ }
+
+ // Simulate reading from the data
+ int bytesRead = Math.min(length, mockData.length -
readCount.get());
+ System.arraycopy(mockData, readCount.get(),
invocation.getArgument(0), offset, bytesRead);
+ readCount.addAndGet(bytesRead);
+ return bytesRead;
+ });
+
+ when(stream.read()).thenAnswer(invocation -> {
+ // Simulate single-byte reads
+ if (readCount.get() >= mockData.length) {
+ return -1; // Simulate EOF
+ }
+ return (int) mockData[readCount.getAndIncrement()];
+ });
+
+ when(dependency.adaptTo(InputStream.class)).thenReturn(stream);
+
+ ScriptNameAwareReader reader = dependencyResolver.resolve(bindings,
SCRIPT_PATH);
+
+ assertNotNull(reader);
+ assertEquals(SCRIPT_PATH, reader.getScriptName());
+
+
+ verify(stream, never()).read(any(), anyInt(), anyInt());
+ verify(stream, never()).read();
+ verify(stream, never()).close();
+ }
+
+
+}