blob: 719a44da6eab4e2b0e3a69c0007e09248b666c2f [file] [log] [blame]
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ 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.impl.engine.compiled;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.classloader.ClassLoaderWriter;
import org.apache.sling.commons.compiler.CompilationResult;
import org.apache.sling.commons.compiler.CompilationUnit;
import org.apache.sling.commons.compiler.JavaCompiler;
import org.apache.sling.commons.compiler.Options;
import org.apache.sling.scripting.api.resource.ScriptingResourceResolverProvider;
import org.apache.sling.scripting.sightly.compiler.SightlyCompiler;
import org.apache.sling.scripting.sightly.impl.compiler.MockPojo;
import org.apache.sling.scripting.sightly.impl.engine.ResourceBackedPojoChangeMonitor;
import org.apache.sling.scripting.sightly.impl.engine.SightlyEngineConfiguration;
import org.apache.sling.scripting.sightly.impl.engine.runtime.RenderContextImpl;
import org.apache.sling.scripting.sightly.render.RenderContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.powermock.reflect.Whitebox;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class SlingHTLMasterCompilerTest {
private SightlyEngineConfiguration sightlyEngineConfiguration;
private ScriptingResourceResolverProvider scriptingResourceResolverProvider;
private SlingHTLMasterCompiler compiler;
private JavaCompiler javaCompiler;
private ResourceBackedPojoChangeMonitor resourceBackedPojoChangeMonitor;
private ClassLoaderWriter classLoaderWriter;
@Before
public void setUp() {
sightlyEngineConfiguration = mock(SightlyEngineConfiguration.class);
when(sightlyEngineConfiguration.getBundleSymbolicName()).thenReturn("org.apache.sling.scripting.sightly");
when(sightlyEngineConfiguration.getEngineVersion()).thenReturn("1.0.17-SNAPSHOT");
when(sightlyEngineConfiguration.getScratchFolder()).thenReturn("/org/apache/sling/scripting/sightly");
ResourceResolver scriptingResourceResolver = mock(ResourceResolver.class);
scriptingResourceResolverProvider = mock(ScriptingResourceResolverProvider.class);
when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(scriptingResourceResolver);
when(scriptingResourceResolver.getSearchPath()).thenReturn(new String[] {"/apps", "/libs"});
compiler = spy(new SlingHTLMasterCompiler());
javaCompiler = mock(JavaCompiler.class);
SightlyCompiler sightlyCompiler = new SightlyCompiler();
classLoaderWriter = Mockito.mock(ClassLoaderWriter.class);
ClassLoader classLoader = Mockito.mock(ClassLoader.class);
when(classLoaderWriter.getClassLoader()).thenReturn(classLoader);
resourceBackedPojoChangeMonitor = spy(new ResourceBackedPojoChangeMonitor());
Whitebox.setInternalState(compiler, "sightlyEngineConfiguration", sightlyEngineConfiguration);
Whitebox.setInternalState(compiler, "resourceBackedPojoChangeMonitor", resourceBackedPojoChangeMonitor);
Whitebox.setInternalState(compiler, "javaCompiler", javaCompiler);
Whitebox.setInternalState(compiler, "sightlyCompiler", sightlyCompiler);
Whitebox.setInternalState(compiler, "classLoaderWriter", classLoaderWriter);
}
@After
public void tearDown() {
sightlyEngineConfiguration = null;
compiler = null;
resourceBackedPojoChangeMonitor = null;
classLoaderWriter = null;
}
@Test
public void testActivateNoPreviousInfo() {
ClassLoaderWriter classLoaderWriter = mock(ClassLoaderWriter.class);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
when(classLoaderWriter.getOutputStream(SlingHTLMasterCompiler.SIGHTLY_CONFIG_FILE)).thenReturn(outputStream);
Whitebox.setInternalState(compiler, "classLoaderWriter", classLoaderWriter);
compiler.activate();
verify(classLoaderWriter).delete(sightlyEngineConfiguration.getScratchFolder());
assertEquals("1.0.17-SNAPSHOT", outputStream.toString());
}
@Test
public void testActivateOverPreviousVersion() {
ClassLoaderWriter classLoaderWriter = mock(ClassLoaderWriter.class);
try {
when(classLoaderWriter.getInputStream(SlingHTLMasterCompiler.SIGHTLY_CONFIG_FILE))
.thenReturn(IOUtils.toInputStream("1.0.16", "UTF-8"));
} catch (IOException e) {
fail("IOException while setting tests.");
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
when(classLoaderWriter.getOutputStream(SlingHTLMasterCompiler.SIGHTLY_CONFIG_FILE)).thenReturn(outputStream);
when(classLoaderWriter.delete(sightlyEngineConfiguration.getScratchFolder())).thenReturn(true);
Whitebox.setInternalState(compiler, "classLoaderWriter", classLoaderWriter);
compiler.activate();
verify(classLoaderWriter).delete(sightlyEngineConfiguration.getScratchFolder());
assertEquals("1.0.17-SNAPSHOT", outputStream.toString());
}
@Test
public void testActivateOverSameVersion() {
ClassLoaderWriter classLoaderWriter = mock(ClassLoaderWriter.class);
try {
when(classLoaderWriter.getInputStream(SlingHTLMasterCompiler.SIGHTLY_CONFIG_FILE))
.thenReturn(IOUtils.toInputStream("1.0.17-SNAPSHOT", "UTF-8"));
} catch (IOException e) {
fail("IOException while setting tests.");
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayOutputStream spyOutputStream = spy(outputStream);
when(classLoaderWriter.getOutputStream(SlingHTLMasterCompiler.SIGHTLY_CONFIG_FILE)).thenReturn(spyOutputStream);
Whitebox.setInternalState(compiler, "classLoaderWriter", classLoaderWriter);
compiler.activate();
verify(classLoaderWriter, never()).delete(sightlyEngineConfiguration.getScratchFolder());
try {
verify(spyOutputStream, never()).write(any(byte[].class));
verify(spyOutputStream, never()).close();
} catch (IOException e) {
fail("IOException in test verification.");
}
}
@Test
public void testDiskCachedUseObject() throws Exception {
String pojoPath = "/apps/myproject/testcomponents/a/Pojo.java";
String className = "apps.myproject.testcomponents.a.Pojo";
scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(resolver);
Resource pojoResource = Mockito.mock(Resource.class);
when(pojoResource.getPath()).thenReturn(pojoPath);
ResourceMetadata mockMetadata = Mockito.mock(ResourceMetadata.class);
when(mockMetadata.getModificationTime()).thenReturn(1L);
when(pojoResource.getResourceMetadata()).thenReturn(mockMetadata);
when(pojoResource.adaptTo(InputStream.class)).thenReturn(IOUtils.toInputStream("DUMMY", "UTF-8"));
when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
when(classLoaderWriter.getLastModified("/apps/myproject/testcomponents/a/Pojo.class")).thenReturn(2L);
getInstancePojoTest(className);
/*
* assuming the compiled class has a last modified date greater than the source, then the compiler should not recompile the Use
* object
*/
verify(javaCompiler, never()).compile(any(CompilationUnit[].class), any(Options.class));
}
@Test
public void testObsoleteDiskCachedUseObject() throws Exception {
String pojoPath = "/apps/myproject/testcomponents/a/Pojo.java";
String className = "apps.myproject.testcomponents.a.Pojo";
scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(resolver);
Resource pojoResource = Mockito.mock(Resource.class);
when(pojoResource.getPath()).thenReturn(pojoPath);
ResourceMetadata mockMetadata = Mockito.mock(ResourceMetadata.class);
when(mockMetadata.getModificationTime()).thenReturn(2L);
when(pojoResource.getResourceMetadata()).thenReturn(mockMetadata);
when(pojoResource.adaptTo(InputStream.class)).thenReturn(IOUtils.toInputStream("DUMMY", "UTF-8"));
when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
when(classLoaderWriter.getLastModified("/apps/myproject/testcomponents/a/Pojo.class")).thenReturn(1L);
getInstancePojoTest(className);
/*
* assuming the compiled class has a last modified date greater than the source, then the compiler should not recompile the Use
* object
*/
verify(compiler).getResourceBackedUseObject(any(RenderContext.class), any(String.class));
}
@Test
public void testMemoryCachedUseObject() throws Exception {
String pojoPath = "/apps/myproject/testcomponents/a/Pojo.java";
String className = "apps.myproject.testcomponents.a.Pojo";
scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(resolver);
Resource pojoResource = Mockito.mock(Resource.class);
when(pojoResource.getPath()).thenReturn(pojoPath);
when(resourceBackedPojoChangeMonitor.getLastModifiedDateForJavaUseObject(pojoPath)).thenReturn(1L);
when(pojoResource.adaptTo(InputStream.class)).thenReturn(IOUtils.toInputStream("DUMMY", "UTF-8"));
when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
when(classLoaderWriter.getLastModified("/apps/myproject/testcomponents/a/Pojo.class")).thenReturn(2L);
getInstancePojoTest(className);
/*
* assuming the compiled class has a last modified date greater than the source, then the compiler should not recompile the Use
* object
*/
verify(javaCompiler, never()).compile(any(CompilationUnit[].class), any(Options.class));
}
@Test
public void testObsoleteMemoryCachedUseObject() throws Exception {
String pojoPath = "/apps/myproject/testcomponents/a/Pojo.java";
String className = "apps.myproject.testcomponents.a.Pojo";
scriptingResourceResolverProvider = Mockito.mock(ScriptingResourceResolverProvider.class);
ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
when(scriptingResourceResolverProvider.getRequestScopedResourceResolver()).thenReturn(resolver);
Resource pojoResource = Mockito.mock(Resource.class);
when(pojoResource.getPath()).thenReturn(pojoPath);
when(resourceBackedPojoChangeMonitor.getLastModifiedDateForJavaUseObject(pojoPath)).thenReturn(2L);
when(pojoResource.adaptTo(InputStream.class)).thenReturn(IOUtils.toInputStream("DUMMY", "UTF-8"));
when(resolver.getResource(pojoPath)).thenReturn(pojoResource);
when(classLoaderWriter.getLastModified("/apps/myproject/testcomponents/a/Pojo.class")).thenReturn(1L);
getInstancePojoTest(className);
/*
* assuming the compiled class has a last modified date greater than the source, then the compiler should not recompile the Use
* object
*/
verify(javaCompiler, times(1)).compile(any(CompilationUnit[].class), isNull());
}
@Test
public void testGetPOJOFromFQCN() {
Map<String, String> expectedScriptNames = new HashMap<String, String>() {{
put("/apps/a_b_c/d_e_f/Pojo.java", "apps.a_b_c.d_e_f.Pojo");
put("/apps/a-b-c/d.e.f/Pojo.java", "apps.a_b_c.d_e_f.Pojo");
put("/apps/a-b-c/d-e.f/Pojo.java", "apps.a_b_c.d_e_f.Pojo");
put("/apps/a-b-c/d.e_f/Pojo.java", "apps.a_b_c.d_e_f.Pojo");
put("/apps/a-b-c/d-e-f/Pojo.java", "apps.a_b_c.d_e_f.Pojo");
put("/apps/a/b/c/Pojo.java", "apps.a.b.c.Pojo");
}};
for (Map.Entry<String, String> scriptEntry : expectedScriptNames.entrySet()) {
ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
Resource resource = Mockito.mock(Resource.class);
when(resource.getPath()).thenReturn(scriptEntry.getKey());
when(resolver.getResource(scriptEntry.getKey())).thenReturn(resource);
Resource result = compiler.getPOJOFromFQCN(resolver, scriptEntry.getValue());
assertNotNull(
String.format("ResourceResolver was expected to find resource %s for POJO %s. Got null instead.", scriptEntry.getKey(),
scriptEntry.getValue()), result);
assertEquals(scriptEntry.getKey(), result.getPath());
}
}
private void getInstancePojoTest(String className) throws Exception {
RenderContextImpl renderContext = mock(RenderContextImpl.class);
CompilationResult compilationResult = mock(CompilationResult.class);
when(compilationResult.getErrors()).thenReturn(new ArrayList<>());
when(javaCompiler.compile(any(CompilationUnit[].class), isNull())).thenReturn(compilationResult);
when(classLoaderWriter.getClassLoader().loadClass(className)).thenAnswer(invocationOnMock -> MockPojo.class);
Whitebox.setInternalState(compiler, "classLoaderWriter", classLoaderWriter);
Whitebox.setInternalState(compiler, "javaCompiler", javaCompiler);
Whitebox.setInternalState(compiler, "scriptingResourceResolverProvider", scriptingResourceResolverProvider);
Whitebox.setInternalState(compiler, "sightlyCompiler", new SightlyCompiler());
Object obj = compiler.getResourceBackedUseObject(renderContext, className);
assertTrue("Expected to obtain a " + MockPojo.class.getName() + " object.", obj instanceof MockPojo);
}
}