/* * Copyright (C) 2015 RoboVM AB * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.robovm.compiler; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.junit.Test; import static org.junit.Assert.*; import org.robovm.compiler.clazz.Clazz; import org.robovm.compiler.clazz.Clazzes; import org.robovm.compiler.clazz.Path; import org.robovm.compiler.config.Config; /** * * @author Jaroslav Tulach */ public class AppCompilerTest { @Test public void testMetainfServiceImplIsAdded() throws Exception { final Path impl1 = new MockPath("META-INF/services/java.lang.Number", "java.lang.Integer"); Clazzes clazzes = createClazzes(impl1); Clazz interfaceClazz = clazzes.load("java/lang/Number"); Set<Clazz> compiled = new HashSet<>(); Set<Clazz> queue = new LinkedHashSet<>(); AppCompiler.addMetaInfImplementations(clazzes, interfaceClazz, compiled, queue); assertEquals("One item added to queue: " + queue, 1, queue.size()); assertTrue("Integer in queue" + queue, queue.contains(clazzes.load("java/lang/Integer"))); } @Test public void testMultipleMetainfServiceImplsAdded() throws Exception { final Path impl1 = new MockPath("META-INF/services/java.lang.Number", "java.lang.Integer"); final Path impl2 = new MockPath("META-INF/services/java.lang.Number", "java.lang.Long"); Clazzes clazzes = createClazzes(impl1, impl2); Clazz interfaceClazz = clazzes.load("java/lang/Number"); Set<Clazz> compiled = new HashSet<>(); Set<Clazz> queue = new LinkedHashSet<>(); AppCompiler.addMetaInfImplementations(clazzes, interfaceClazz, compiled, queue); assertEquals("Two items added to queue: " + queue, 2, queue.size()); assertTrue("Integer in queue" + queue, queue.contains(clazzes.load("java/lang/Integer"))); assertTrue("Long in queue" + queue, queue.contains(clazzes.load("java/lang/Long"))); } @Test public void testMultilineFile() throws Exception { final Path impl1 = new MockPath("META-INF/services/java.lang.Number", "# first register Integer\n" + "java.lang.Integer\n" + "# then add Long\n" + "java.lang.Long\n" + "\n\n\n\n" ); Clazzes clazzes = createClazzes(impl1); Clazz interfaceClazz = clazzes.load("java/lang/Number"); Set<Clazz> compiled = new HashSet<>(); Set<Clazz> queue = new LinkedHashSet<>(); AppCompiler.addMetaInfImplementations(clazzes, interfaceClazz, compiled, queue); assertEquals("Two items added to queue: " + queue, 2, queue.size()); assertTrue("Integer in queue" + queue, queue.contains(clazzes.load("java/lang/Integer"))); assertTrue("Long in queue" + queue, queue.contains(clazzes.load("java/lang/Long"))); } @Test public void testMissingImplIsIgnore() throws Exception { final Path impl1 = new MockPath("META-INF/services/java.lang.Number", "java.lang.Integer"); final Path impl2 = new MockPath("META-INF/services/java.lang.Number", "nobody.knows.such.Class"); Clazzes clazzes = createClazzes(impl1, impl2); Clazz interfaceClazz = clazzes.load("java/lang/Number"); Set<Clazz> compiled = new HashSet<>(); Set<Clazz> queue = new LinkedHashSet<>(); AppCompiler.addMetaInfImplementations(clazzes, interfaceClazz, compiled, queue); assertEquals("Just one item added to queue: " + queue, 1, queue.size()); assertTrue("Integer in queue" + queue, queue.contains(clazzes.load("java/lang/Integer"))); } @Test public void allStreamsAreClosedInCaseOfFailure() throws Exception { final MockPath impl1 = new MockPath("META-INF/services/java.lang.Number", "java.lang.Integer"); impl1.toThrow = new IOException(); final MockPath impl2 = new MockPath("META-INF/services/java.lang.Number", "nobody.knows.such.Class"); Clazzes clazzes = createClazzes(impl1, impl2); Clazz interfaceClazz = clazzes.load("java/lang/Number"); Set<Clazz> compiled = new HashSet<>(); Set<Clazz> queue = new LinkedHashSet<>(); try { AppCompiler.addMetaInfImplementations(clazzes, interfaceClazz, compiled, queue); fail("Should throw an exception"); } catch (IOException ex) { assertSame("Our exception is thrown", impl1.toThrow, ex); } assertTrue("First stream is closed", impl1.closed); assertTrue("Second stream is closed", impl2.closed); } private static Clazzes createClazzes(final Path... paths) throws Exception { File home = new File(System.getProperty("java.home")); Config cfg = new Config() { }; Clazzes clazzes = new Clazzes( cfg, Collections.nCopies(1, new File(new File(home, "lib"), "rt.jar")), Collections.<File>emptyList() ) { @Override public List<Path> getPaths() { return Arrays.asList(paths); } }; return clazzes; } private static final class MockPath implements Path { private final String file; private final String content; private IOException toThrow; private boolean closed; public MockPath(String file, String content) { this.file = file; this.content = content; } @Override public int getIndex() { throw new UnsupportedOperationException(); } @Override public File getFile() { throw new UnsupportedOperationException(); } @Override public Set<Clazz> listClasses() { throw new UnsupportedOperationException(); } @Override public Clazz loadGeneratedClass(String internalName) { throw new UnsupportedOperationException(); } @Override public File getGeneratedClassFile(String internalName) { throw new UnsupportedOperationException(); } @Override public boolean hasChangedSince(long timestamp) { throw new UnsupportedOperationException(); } @Override public boolean isInBootClasspath() { throw new UnsupportedOperationException(); } @Override public boolean contains(String file) { return this.file.equals(file); } @Override public InputStream open(String file) throws IOException { if (this.file.equals(file)) { ByteArrayInputStream is = new ByteArrayInputStream(this.content.getBytes("UTF-8")); return new FilterInputStream(is) { @Override public synchronized int read(byte[] b, int off, int len) throws IOException { if (toThrow != null) { throw toThrow; } return super.read(b, off, len); } @Override public void close() throws IOException { closed = true; super.close(); } }; } throw new IOException(); } } }