/* * 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.shindig.gadgets.features; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.shindig.common.uri.Uri; import org.apache.shindig.common.uri.UriBuilder; import org.apache.shindig.config.ContainerConfig; import org.apache.shindig.gadgets.GadgetContext; import org.apache.shindig.gadgets.GadgetException; import org.apache.shindig.gadgets.RenderingContext; import org.junit.Before; import org.junit.Test; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; public class FeatureRegistryTest { private static final String NODEP_TPL = getFeatureTpl("nodep", new String[] {}); private static final String TOP_TPL = getFeatureTpl("top", new String[] { "mid_a", "mid_b" }); private static final String MID_A_TPL = getFeatureTpl("mid_a", new String[] { "bottom" }); private static final String MID_B_TPL = getFeatureTpl("mid_b", new String[] { "bottom" }); private static final String BOTTOM_TPL = getFeatureTpl("bottom", new String[] {}); private static final String LOOP_A_TPL = getFeatureTpl("loop_a", new String[] { "loop_b" }); private static final String LOOP_B_TPL = getFeatureTpl("loop_b", new String[] { "loop_c" }); private static final String LOOP_C_TPL = getFeatureTpl("loop_c", new String[] { "loop_a" }); private static final String BAD_DEP_TPL = getFeatureTpl("bad_dep", new String[] { "no-exists" }); private static String RESOURCE_BASE_PATH = "/resource/base/path"; private static int resourceIdx = 0; private FeatureResourceLoader resourceLoader; private ResourceMock resourceMock; private FeatureRegistry registry; private Map<String, String> lastAttribs; @Before public void setUp() { resourceMock = new ResourceMock(); lastAttribs = null; resourceLoader = new FeatureResourceLoader() { public FeatureResource load(Uri uri, Map<String, String> attribs) throws GadgetException { lastAttribs = ImmutableMap.copyOf(attribs); return super.load(uri, attribs); } @Override protected String getResourceContent(String resource) { try { return resourceMock.get(resource); } catch (IOException e) { return null; } } }; } private class TestFeatureRegistry extends FeatureRegistry { TestFeatureRegistry(String featureFiles) throws GadgetException { super(resourceLoader, ImmutableList.<String>of(featureFiles)); } @Override String getResourceContent(String resource) throws IOException { return resourceMock.get(resource); } } @Test public void registerFromFileFeatureXmlFileScheme() throws Exception { checkRegisterFromFileFeatureXml(true); } @Test public void registerFromFileFeatureXmlNoScheme() throws Exception { checkRegisterFromFileFeatureXml(false); } private void checkRegisterFromFileFeatureXml(boolean withScheme) throws Exception { String content = "content-" + (withScheme ? "withScheme" : "noScheme"); Uri resUri = makeFile(content); Uri featureFile = makeFile(xml(NODEP_TPL, "gadget", withScheme ? resUri.toString() : resUri.getPath(), null)); registry = new TestFeatureRegistry(withScheme ? featureFile.toString() : featureFile.getPath()); // Verify single resource works all the way through. List<FeatureResource> resources = registry.getAllFeatures(); assertEquals(1, resources.size()); assertEquals(content, resources.get(0).getContent()); } @Test public void registerFromFileInNestedDirectoryFeatureXmlFile() throws Exception { // Get the directory from dummyUri and create a subdir. File tmpFile = File.createTempFile("dummy", ".dat"); tmpFile.deleteOnExit(); File parentDir = tmpFile.getParentFile(); String childDirName = String.valueOf(Math.random()); File childDir = new File(parentDir, childDirName); childDir.mkdirs(); childDir.deleteOnExit(); File featureDir = new File(childDir, "thefeature"); featureDir.mkdirs(); featureDir.deleteOnExit(); File resFile = File.createTempFile("content", ".js", featureDir); resFile.deleteOnExit(); String content = "content-foo"; BufferedWriter out = new BufferedWriter(new FileWriter(resFile)); out.write(content); out.close(); File featureFile = File.createTempFile("feature", ".xml", featureDir); featureFile.deleteOnExit(); out = new BufferedWriter(new FileWriter(featureFile)); out.write(xml(NODEP_TPL, "gadget", resFile.toURI().toString(), null)); out.close(); registry = new TestFeatureRegistry(childDir.toURI().toString()); // Verify single resource works all the way through. List<FeatureResource> resources = registry.getAllFeatures(); assertEquals(1, resources.size()); assertEquals(content, resources.get(0).getContent()); } @Test public void registerFromResourceFeatureXml() throws Exception { String content = "resource-content()"; Uri contentUri = expectResource(content); Uri featureUri = expectResource(xml(NODEP_TPL, "gadget", contentUri.getPath(), null)); registry = new TestFeatureRegistry(featureUri.toString()); // Verify single resource works all the way through. List<FeatureResource> resources = registry.getAllFeatures(); assertEquals(1, resources.size()); assertEquals(content, resources.get(0).getContent()); } @Test public void registerFromResourceFeatureXmlRelativeContent() throws Exception { String content = "resource-content-relative()"; Uri contentUri = expectResource(content); String relativePath = contentUri.getPath().substring(contentUri.getPath().lastIndexOf('/') + 1); Uri featureUri = expectResource(xml(NODEP_TPL, "gadget", relativePath, null)); registry = new TestFeatureRegistry(featureUri.toString()); // Verify single resource works all the way through. List<FeatureResource> resources = registry.getAllFeatures(); assertEquals(1, resources.size()); assertEquals(content, resources.get(0).getContent()); } @Test public void registerFromResourceIndex() throws Exception { // One with extern resource loaded content... String content1 = "content1()"; Uri content1Uri = expectResource(content1); Uri feature1Uri = expectResource(xml(MID_A_TPL, "gadget", content1Uri.getPath(), null)); // One feature with inline content (that it depends on)... String content2 = "inline()"; Uri feature2Uri = expectResource(xml(BOTTOM_TPL, "gadget", null, content2)); // .txt file to join the two Uri txtFile = expectResource(feature1Uri.toString() + '\n' + feature2Uri.toString(), ".txt"); // Load resources from the text file and do basic validation they're good. registry = new TestFeatureRegistry(txtFile.toString()); // Contents should be ordered based on the way they went in. List<FeatureResource> resources = registry.getAllFeatures(); assertEquals(2, resources.size()); assertEquals(content2, resources.get(0).getContent()); assertEquals(content1, resources.get(1).getContent()); } @Test public void registerOverrideFeature() throws Exception { // Feature 1 String content1 = "content1()"; Uri content1Uri = expectResource(content1); Uri feature1Uri = expectResource(xml(BOTTOM_TPL, "gadget", content1Uri.getPath(), null)); String content2 = "content_two()"; Uri content2Uri = expectResource(content2); Uri feature2Uri = expectResource(xml(BOTTOM_TPL, "gadget", content2Uri.getPath(), null)); registry = new TestFeatureRegistry(feature1Uri.toString()); List<FeatureResource> resources1 = registry.getAllFeatures(); assertEquals(1, resources1.size()); assertEquals(content1, resources1.get(0).getContent()); // Register it again, different def. registry = new TestFeatureRegistry(feature2Uri.toString()); List<FeatureResource> resources2 = registry.getAllFeatures(); assertEquals(1, resources2.size()); assertEquals(content2, resources2.get(0).getContent()); // Check cached resources too. List<FeatureResource> resourcesAgain = registry.getAllFeatures(); assertSame(resources2, resourcesAgain); } @Test public void cacheAccountsForUnsupportedState() throws Exception { String content1 = "content1()"; Uri content1Uri = expectResource(content1); Map<String, String> attribs = Maps.newHashMap(); String theContainer = "the-container"; attribs.put("container", theContainer); Uri feature1Uri = expectResource(xml(BOTTOM_TPL, "gadget", content1Uri.getPath(), null, attribs)); // Register it. registry = new TestFeatureRegistry(feature1Uri.toString()); // Retrieve content for matching context. List<String> needed = Lists.newArrayList("bottom"); List<String> unsupported = Lists.newArrayList(); List<FeatureResource> resources = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer), needed, unsupported); // Retrieve again w/ no unsupported list. List<FeatureResource> resourcesUnsup = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer), needed, null); assertNotSame(resources, resourcesUnsup); assertEquals(resources, resourcesUnsup); assertEquals(1, resources.size()); assertEquals(content1, resources.get(0).getContent()); // Now make sure the cache DOES work when needed. List<FeatureResource> resources2 = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer), needed, unsupported); assertSame(resources, resources2); List<FeatureResource> resourcesUnsup2 = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer), needed, null); assertSame(resourcesUnsup, resourcesUnsup2); // Lastly, ensure that ignoreCache is properly accounted. List<FeatureResource> resourcesIgnoreCache = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer, true), needed, unsupported); assertNotSame(resources, resourcesIgnoreCache); assertEquals(1, resourcesIgnoreCache.size()); assertEquals(content1, resourcesIgnoreCache.get(0).getContent()); } @Test public void cacheAccountsForContext() throws Exception { String content1 = "content1()"; Uri content1Uri = expectResource(content1); Map<String, String> attribs = Maps.newHashMap(); String theContainer = "the-container"; attribs.put("container", theContainer); Uri feature1Uri = expectResource(xml(BOTTOM_TPL, "gadget", content1Uri.getPath(), null, attribs)); // Register it. registry = new TestFeatureRegistry(feature1Uri.toString()); // Retrieve content for matching context. List<String> needed = Lists.newArrayList("bottom"); List<String> unsupported = Lists.newArrayList(); List<FeatureResource> resources = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer), needed, unsupported); // Retrieve again w/ mismatch container. List<FeatureResource> resourcesNoMatch = registry.getFeatureResources( getCtx(RenderingContext.GADGET, "foo"), needed, unsupported); // Retrieve again w/ mismatched context. List<FeatureResource> ctxMismatch = registry.getFeatureResources( getCtx(RenderingContext.CONTAINER, theContainer), needed, unsupported); assertNotSame(resources, resourcesNoMatch); assertNotSame(resources, ctxMismatch); assertEquals(1, resources.size()); assertEquals(content1, resources.get(0).getContent()); assertEquals(0, resourcesNoMatch.size()); assertEquals(0, ctxMismatch.size()); // Make sure caches work with appropriate matching. List<FeatureResource> resources2 = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer), needed, unsupported); assertSame(resources, resources2); List<FeatureResource> resourcesNoMatch2 = registry.getFeatureResources( getCtx(RenderingContext.GADGET, "foo"), needed, unsupported); assertSame(resourcesNoMatch, resourcesNoMatch2); List<FeatureResource> ctxMismatch2 = registry.getFeatureResources( getCtx(RenderingContext.CONTAINER, theContainer), needed, unsupported); assertSame(ctxMismatch, ctxMismatch2); // Check ignoreCache List<FeatureResource> resourcesIC = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer, true), needed, unsupported); assertNotSame(resources, resourcesIC); // bogus tests - both requests return EMPTY_LIST now, so you can't ascertain cache behavior. // List<FeatureResource> resourcesNoMatchIC = registry.getFeatureResources( // getCtx(RenderingContext.GADGET, "foo", true), needed, unsupported); // assertNotSame(resourcesNoMatch, resourcesNoMatchIC); // // List<FeatureResource> ctxMismatchIC = registry.getFeatureResources( // getCtx(RenderingContext.CONTAINER, theContainer, true), needed, unsupported); // assertNotSame(ctxMismatch, ctxMismatchIC); } @Test public void missingIndexResultsInException() throws Exception { try { registry = new TestFeatureRegistry(makeResourceUri(".txt").toString()); fail("Should have thrown an exception for missing .txt file"); } catch (GadgetException e) { // Expected. Verify code. assertEquals(GadgetException.Code.INVALID_PATH, e.getCode()); } } @Test public void missingFileResultsInException() throws Exception { try { registry = new TestFeatureRegistry(new UriBuilder().setScheme("file") .setPath("/is/not/there.foo.xml").toUri().toString()); fail("Should have thrown missing .xml file exception"); } catch (GadgetException e) { // Expected. Verify code. assertEquals(GadgetException.Code.INVALID_CONFIG, e.getCode()); } } @Test public void selectExactFeatureResourcesGadget() throws Exception { checkExactFeatureResources("gadget", RenderingContext.GADGET); } @Test public void selectExactFeatureResourcesContainer() throws Exception { checkExactFeatureResources("container", RenderingContext.CONTAINER); } private void checkExactFeatureResources(String type, RenderingContext rctx) throws Exception { setupFullRegistry(type, null); GadgetContext ctx = getCtx(rctx, null); List<String> needed = Lists.newArrayList("nodep", "bottom"); List<String> unsupported = Lists.newLinkedList(); List<FeatureResource> resources = registry.getFeatureResources(ctx, needed, unsupported); assertEquals(0, unsupported.size()); assertEquals(2, resources.size()); assertEquals("nodep", resources.get(0).getContent()); assertEquals("bottom", resources.get(1).getContent()); } @Test public void selectNoContentValidFeatureResourcesGadget() throws Exception { checkNoContentValidFeatureResources("gadget", RenderingContext.CONTAINER); } @Test public void selectNoContentValidFeatureResourcesContainer() throws Exception { checkNoContentValidFeatureResources("container", RenderingContext.GADGET); } private void checkNoContentValidFeatureResources( String type, RenderingContext rctx) throws Exception { setupFullRegistry(type, null); GadgetContext ctx = getCtx(rctx, null); List<String> needed = Lists.newArrayList("nodep", "bottom"); List<String> unsupported = Lists.newLinkedList(); List<FeatureResource> resources = registry.getFeatureResources(ctx, needed, unsupported); assertEquals(0, resources.size()); } @Test public void testTransitiveFeatureResourcesGadget() throws Exception { checkTransitiveFeatureResources("gadget", RenderingContext.GADGET); } @Test public void testTransitiveFeatureResourcesContainer() throws Exception { checkTransitiveFeatureResources("container", RenderingContext.CONTAINER); } private void checkTransitiveFeatureResources(String type, RenderingContext rctx) throws Exception { setupFullRegistry(type, null); GadgetContext ctx = getCtx(rctx, null); List<String> needed = Lists.newArrayList("top", "nodep"); List<String> unsupported = Lists.newLinkedList(); // Should come back in insertable order (from bottom of the graph up), // querying in feature.xml dependency order. List<FeatureResource> resources = registry.getFeatureResources(ctx, needed, unsupported); assertEquals(5, resources.size()); assertEquals("bottom", resources.get(0).getContent()); assertEquals("mid_a", resources.get(1).getContent()); assertEquals("mid_b", resources.get(2).getContent()); assertEquals("top", resources.get(3).getContent()); assertEquals("nodep", resources.get(4).getContent()); } @Test public void unsupportedFeaturesPopulated() throws Exception { // Test only for gadget case; above tests are sufficient to ensure // that type and RenderingContext filter results properly. setupFullRegistry("gadget", null); GadgetContext ctx = getCtx(RenderingContext.GADGET, null); List<String> needed = Lists.newArrayList("nodep", "does-not-exist"); List<String> unsupported = Lists.newLinkedList(); List<FeatureResource> resources = registry.getFeatureResources(ctx, needed, unsupported); assertEquals(1, resources.size()); assertEquals("nodep", resources.get(0).getContent()); assertEquals(1, unsupported.size()); assertEquals("does-not-exist", unsupported.get(0)); } @Test public void filterFeaturesByContainerMatch() throws Exception { // Again test only for gadget case; above tests cover type <-> RenderingContext setupFullRegistry("gadget", "one, two , three"); GadgetContext ctx = getCtx(RenderingContext.GADGET, "two"); List<String> needed = Lists.newArrayList("nodep", "bottom"); List<String> unsupported = Lists.newLinkedList(); List<FeatureResource> resources = registry.getFeatureResources(ctx, needed, unsupported); assertEquals(2, resources.size()); assertEquals("nodep", resources.get(0).getContent()); assertEquals("bottom", resources.get(1).getContent()); assertEquals(0, unsupported.size()); } @Test public void filterFeaturesByContainerNoMatch() throws Exception { // Again test only for gadget case; above tests cover type <-> RenderingContext setupFullRegistry("gadget", "one, two, three"); GadgetContext ctx = getCtx(RenderingContext.GADGET, "four"); List<String> needed = Lists.newArrayList("nodep", "bottom"); List<String> unsupported = Lists.newLinkedList(); List<FeatureResource> resources = registry.getFeatureResources(ctx, needed, unsupported); assertEquals(0, resources.size()); // no resource matches but all feature keys valid assertEquals(0, unsupported.size()); } @Test public void getFeatureResourcesNoTransitiveSingle() throws Exception { setupFullRegistry("gadget", null); GadgetContext ctx = getCtx(RenderingContext.GADGET, null); List<String> needed = Lists.newArrayList("top", "bottom"); List<String> unsupported = Lists.newLinkedList(); List<FeatureResource> resources = registry.getFeatureResources(ctx, needed, unsupported, false); // Should return in order requested. assertEquals(2, resources.size()); assertEquals("top", resources.get(0).getContent()); assertEquals("bottom", resources.get(1).getContent()); assertEquals(0, unsupported.size()); } @Test public void getAllFeatures() throws Exception { setupFullRegistry("gadget", null); List<FeatureResource> resources = registry.getAllFeatures(); // No guaranteed order (top/mid/bottom bundle may be before nodep) // Just check that there are 5 resources around and let the above tests // handle transitivity checks. assertEquals(5, resources.size()); } @Test public void getFeaturesStringsNoTransitive() throws Exception { setupFullRegistry("gadget", null); List<String> needed = Lists.newArrayList("nodep", "bottom"); List<String> featureNames = registry.getFeatures(needed); assertEquals(2, featureNames.size()); assertEquals("nodep", featureNames.get(0)); assertEquals("bottom", featureNames.get(1)); } @Test public void getFeaturesStringsTransitive() throws Exception { setupFullRegistry("gadget", null); List<String> needed = Lists.newArrayList("top", "nodep"); List<String> featureNames = registry.getFeatures(needed); assertEquals(5, featureNames.size()); assertEquals("bottom", featureNames.get(0)); assertEquals("mid_a", featureNames.get(1)); assertEquals("mid_b", featureNames.get(2)); assertEquals("top", featureNames.get(3)); assertEquals("nodep", featureNames.get(4)); } @Test public void loopIsDetectedAndCrashes() throws Exception { // Set up a registry with features loop_a,b,c. C points back to A, which should // cause an exception to be thrown by the register method. String type = "gadget"; Uri loopAUri = expectResource(xml(LOOP_A_TPL, type, null, "loop_a")); Uri loopBUri = expectResource(xml(LOOP_B_TPL, type, null, "loop_b")); Uri loopCUri = expectResource(xml(LOOP_C_TPL, type, null, "loop_c")); Uri txtFile = expectResource(loopAUri.toString() + '\n' + loopBUri.toString() + '\n' + loopCUri.toString(), ".txt"); try { registry = new TestFeatureRegistry(txtFile.toString()); fail("Should have thrown a loop-detected exception"); } catch (GadgetException e) { assertEquals(GadgetException.Code.INVALID_CONFIG, e.getCode()); } } @Test public void unavailableFeatureCrashes() throws Exception { Uri featUri = expectResource(xml(BAD_DEP_TPL, "gadget", null, "content")); try { registry = new TestFeatureRegistry(featUri.toString()); } catch (GadgetException e) { assertEquals(GadgetException.Code.INVALID_CONFIG, e.getCode()); } } @Test public void returnOnlyContainerFilteredJs() throws Exception { String feature = "thefeature"; String container = "foo"; String containerContent = "content1();"; String defaultContent = "content2();"; Uri featureUri = expectResource( getContainerAndDefaultTpl(feature, container, containerContent, defaultContent)); registry = new TestFeatureRegistry(featureUri.toString()); List<String> needed = Lists.newArrayList(feature); List<String> unsupported = Lists.newLinkedList(); List<FeatureResource> resources = registry.getFeatureResources( getCtx(RenderingContext.GADGET, container), needed, unsupported); assertEquals(1, resources.size()); assertEquals(containerContent, resources.get(0).getContent()); } @Test public void returnDefaultMatchJs() throws Exception { String feature = "thefeature"; String container = "foo"; String containerContent = "content1();"; String defaultContent = "content2();"; Uri featureUri = expectResource( getContainerAndDefaultTpl(feature, container, containerContent, defaultContent)); registry = new TestFeatureRegistry(featureUri.toString()); List<String> needed = Lists.newArrayList(feature); List<String> unsupported = Lists.newLinkedList(); List<FeatureResource> resources = registry.getFeatureResources( getCtx(RenderingContext.GADGET, "othercontainer"), needed, unsupported); assertEquals(1, resources.size()); assertEquals(defaultContent, resources.get(0).getContent()); } private String getContainerAndDefaultTpl(String name, String container, String c1, String c2) { StringBuilder sb = new StringBuilder(); sb.append("<feature><name>").append(name).append("</name>"); sb.append("<gadget container=\"").append(container).append("\">"); sb.append("<script>").append(c1).append("</script></gadget>"); sb.append("<gadget>"); sb.append("<script>").append(c2).append("</script></gadget>"); sb.append("</feature>"); return sb.toString(); } @Test public void resourceGetsMergedAttribs() throws Exception { String content1 = "content1()"; Uri content1Uri = expectResource(content1); Map<String, String> attribs = Maps.newHashMap(); String theContainer = "the-container"; attribs.put("container", theContainer); attribs.put("one", "bundle-one"); attribs.put("two", "bundle-two"); Map<String, String> resourceAttribs = Maps.newHashMap(); attribs.put("two", "attrib-two"); attribs.put("three", "attrib-three"); Uri feature1Uri = expectResource(xml(BOTTOM_TPL, "gadget", content1Uri.getPath(), null, attribs, resourceAttribs)); // Register it. registry = new TestFeatureRegistry(feature1Uri.toString()); // Retrieve the resource for matching context. List<String> needed = Lists.newArrayList("bottom"); List<String> unsupported = Lists.newArrayList(); List<FeatureResource> resources = registry.getFeatureResources( getCtx(RenderingContext.GADGET, theContainer), needed, unsupported); // Sanity test. assertEquals(1, resources.size()); // Check the attribs passed into the resource. This is a little funky, but it works. assertNotNull(lastAttribs); assertEquals(4, lastAttribs.size()); assertEquals(theContainer, lastAttribs.get("container")); assertEquals("bundle-one", lastAttribs.get("one")); assertEquals("attrib-two", lastAttribs.get("two")); assertEquals("attrib-three", lastAttribs.get("three")); } private GadgetContext getCtx(final RenderingContext rctx, final String container) { return getCtx(rctx, container, false); } private GadgetContext getCtx(final RenderingContext rctx, final String container, final boolean ignoreCache) { return new GadgetContext() { @Override public RenderingContext getRenderingContext() { return rctx; } @Override public String getContainer() { return container != null ? container : ContainerConfig.DEFAULT_CONTAINER; } @Override public boolean getIgnoreCache() { return ignoreCache; } }; } private void setupFullRegistry(String type, String containers) throws Exception { // Sets up a "full" gadget feature registry with several features registered: // nodep - has no deps on anything else // top - depends on mid_a and mid_b // mid_a and mid_b - both depend on bottom // bottom - depends on nothing else // The content registered for each is equal to the feature's name, for simplicity. // Also, all content is loaded as inline, also for simplicity. Map<String, String> attribs = Maps.newHashMap(); if (containers != null) { attribs.put("container", containers); } Uri nodepUri = expectResource(xml(NODEP_TPL, type, null, "nodep", attribs)); Uri topUri = expectResource(xml(TOP_TPL, type, null, "top", attribs)); Uri midAUri = expectResource(xml(MID_A_TPL, type, null, "mid_a", attribs)); Uri midBUri = expectResource(xml(MID_B_TPL, type, null, "mid_b", attribs)); Uri bottomUri = expectResource(xml(BOTTOM_TPL, type, null, "bottom", attribs)); Uri txtFile = expectResource(nodepUri.toString() + '\n' + topUri.toString() + '\n' + midAUri.toString() + '\n' + midBUri.toString() + '\n' + bottomUri.toString(), ".txt"); registry = new TestFeatureRegistry(txtFile.toString()); } private Uri expectResource(String content) { return expectResource(content, ".xml"); } private Uri expectResource(String content, String suffix) { Uri res = makeResourceUri(suffix); resourceMock.put(res.getPath(), content); return res; } private static String getFeatureTpl(String name, String[] deps) { StringBuilder sb = new StringBuilder(); sb.append("<feature><name>").append(name).append("</name>"); for (String dep : deps) { sb.append("<dependency>").append(dep).append("</dependency>"); } sb.append("<%type% %type_attribs%><script %uri% %res_attribs%>%content%</script></%type%>"); sb.append("</feature>"); return sb.toString(); } private static String xml(String tpl, String type, String uri, String content) { return xml(tpl, type, uri, content, Maps.<String, String>newHashMap()); } private static String xml(String tpl, String type, String uri, String content, Map<String, String> attribs) { return xml(tpl, type, uri, content, attribs, Maps.<String, String>newHashMap()); } private static String xml(String tpl, String type, String uri, String content, Map<String, String> attribs, Map<String, String> resourceAttribs) { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> entry : attribs.entrySet()) { sb.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\" "); } StringBuilder sbRes = new StringBuilder(); for (Map.Entry<String, String> entry : resourceAttribs.entrySet()) { sbRes.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\" "); } return tpl.replace("%type%", type) .replace("%uri%", uri != null ? "src=\"" + uri + '\"' : "") .replace("%content%", content != null ? content : "") .replace("%type_attribs%", sb.toString()) .replace("%res_attribs%", sbRes.toString()); } private static Uri makeFile(String content) throws Exception { // .xml suffix used even for js -- should be OK per FeatureResourceLoader tests // which simply indicate not to attempt .opt.js loading in this case. File file = File.createTempFile("feat", ".xml"); file.deleteOnExit(); BufferedWriter out = new BufferedWriter(new FileWriter(file)); out.write(content); out.close(); return Uri.fromJavaUri(file.toURI()); } private static Uri makeResourceUri(String suffix) { return Uri.parse("res://" + RESOURCE_BASE_PATH + "/file" + (++resourceIdx) + suffix); } private static class ResourceMock { private final Map<String, String> resourceMap; private ResourceMock() { this.resourceMap = Maps.newHashMap(); } private void put(String key, String value) { resourceMap.put(clean(key), value); } private String get(String key) throws IOException { key = clean(key); if (!resourceMap.containsKey(key)) { throw new IOException("Missing resource: " + key); } return resourceMap.get(key); } private String clean(String key) { // Resource loading doesn't support leading '/' return key.startsWith("/") ? key.substring(1) : key; } } }