/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* 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 distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.compiler.java.test.cmr;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.redhat.ceylon.compiler.java.launcher.Main.ExitState;
import com.redhat.ceylon.compiler.java.test.CompilerError;
import com.redhat.ceylon.compiler.java.test.CompilerTests;
import com.redhat.ceylon.compiler.java.tools.CeyloncTaskImpl;
import com.sun.net.httpserver.HttpServer;
public class CMRHTTPTests extends CompilerTests {
interface ExpectedError {}
enum TimeoutIn implements ExpectedError {
None, Head, GetInitial, GetMiddle, PutInitial, PutMiddle;
}
enum HttpError implements ExpectedError {
FORBIDDEN;
}
class RequestCounter{
volatile int count;
synchronized void add(){
count++;
}
synchronized void check(int count){
Assert.assertEquals(count, this.count);
}
}
private static AtomicInteger PORT_NUM_ALLOCATOR;
@BeforeClass
public static void initPortAllocator() {
PORT_NUM_ALLOCATOR = new AtomicInteger(18000);
}
@AfterClass
public static void cleanupPortAllocator() {
PORT_NUM_ALLOCATOR = null;
}
private int allocPortForTest() {
return PORT_NUM_ALLOCATOR.getAndIncrement();
}
private String getRepoUrl(int allocatedPort) {
return "http://localhost:"+allocatedPort+"/repo";
}
private HttpServer startServer(int port, File repo, boolean herd, RequestCounter rq) throws IOException{
return startServer(port, repo, herd, rq, TimeoutIn.None);
}
private HttpServer startServer(int port, File repo, boolean herd, RequestCounter rq, ExpectedError error) throws IOException{
HttpServer server = HttpServer.create(new InetSocketAddress(port), 1);
server.createContext("/repo", new RepoFileHandler(repo.getPath(), herd, rq, error));
// make sure we serve at least two concurrent connections, as each one might take a few ms to close
ThreadPoolExecutor tpool = (ThreadPoolExecutor)Executors.newFixedThreadPool(2);
server.setExecutor(tpool);
server.start();
return server;
}
private File makeRepo() {
File repo = new File("build/test-cars-http");
cleanCars(repo.getPath());
repo.mkdirs();
return repo;
}
@Test
public void testMdlHTTPRepos() throws IOException {
RequestCounter rq = new RequestCounter();
String moduleA = "com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a";
// Clean up any cached version
File carFileInCache = getModuleArchive(moduleA, "6.6.6", cacheDir);
if(carFileInCache.exists())
carFileInCache.delete();
// Compile the first module in its own repo
File repo = makeRepo();
Boolean result = getCompilerTask(Arrays.asList("-out", repo.getPath()),
"modules/depend/a/module.ceylon", "modules/depend/a/package.ceylon", "modules/depend/a/A.ceylon").call();
Assert.assertEquals(Boolean.TRUE, result);
File carFile = getModuleArchive(moduleA, "6.6.6", repo.getPath());
assertTrue(carFile.exists());
final int port = allocPortForTest();
final String repoAURL = getRepoUrl(port);
// now serve the first repo over HTTP
HttpServer server = startServer(port, repo, false, rq);
try{
// then try to compile only one module (the other being loaded from its car)
result = getCompilerTask(Arrays.asList("-out", destDir, "-rep", repoAURL, "-verbose:cmr", "-cp", getClassPathAsPath()),
"modules/depend/b/module.ceylon", "modules/depend/b/package.ceylon", "modules/depend/b/a.ceylon", "modules/depend/b/B.ceylon").call();
Assert.assertEquals(Boolean.TRUE, result);
}finally{
server.stop(1);
}
carFile = getModuleArchive("com.redhat.ceylon.compiler.java.test.cmr.modules.depend.b", "6.6.6");
assertTrue(carFile.exists());
// make sure it cached the module in the cache repo
assertTrue(carFileInCache.exists());
// make sure it didn't cache it in the output repo
carFile = getModuleArchive(moduleA, "6.6.6");
assertFalse(carFile.exists());
rq.check(37);
}
@Test
public void testMdlHTTPTimeout() throws IOException {
String moduleA = "com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a";
// Clean up any cached version
File carFileInCache = getModuleArchive(moduleA, "6.6.6", cacheDir);
if(carFileInCache.exists())
carFileInCache.delete();
// Compile the first module in its own repo
File repo = makeRepo();
Boolean result = getCompilerTask(Arrays.asList("-out", repo.getPath()),
"modules/depend/a/module.ceylon", "modules/depend/a/package.ceylon", "modules/depend/a/A.ceylon").call();
Assert.assertEquals(Boolean.TRUE, result);
File carFile = getModuleArchive(moduleA, "6.6.6", repo.getPath());
assertTrue(carFile.exists());
final int port = allocPortForTest();
final String repoAURL = getRepoUrl(port);
String[] files = new String[]{
"modules/depend/b/module.ceylon",
"modules/depend/b/package.ceylon",
"modules/depend/b/a.ceylon",
"modules/depend/b/B.ceylon"
};
List<String> options = Arrays.asList("-out", destDir, "-rep", repoAURL, "-verbose:cmr",
"-cp", getClassPathAsPath(), "-timeout", "1");
// now serve the first repo over HTTP
HttpServer server = startServer(port, repo, false, null, TimeoutIn.GetMiddle);
try{
// then try to compile only one module (the other being loaded from its car)
assertErrors(files, options, null,
new CompilerError(24, "cannot find module artifact com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a-6.6.6.car\n"+
" due to connection error: java.net.SocketTimeoutException: Timed out reading com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a-6.6.6.car from "+repoAURL+"\n"+
" \t- dependency tree: com.redhat.ceylon.compiler.java.test.cmr.modules.depend.b/6.6.6 -> com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a/6.6.6"));
}finally{
server.stop(1);
}
if(carFileInCache.exists())
carFileInCache.delete();
server = startServer(port, repo, false, null, TimeoutIn.GetInitial);
try{
// then try to compile only one module (the other being loaded from its car)
assertErrors(files, options, null,
new CompilerError(24, "cannot find module artifact com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a-6.6.6.car\n"+
" due to connection error: java.net.SocketTimeoutException: Timed out during connection to "+repoAURL+"/com/redhat/ceylon/compiler/java/test/cmr/modules/depend/a/6.6.6/com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a-6.6.6.car\n"+
" \t- dependency tree: com.redhat.ceylon.compiler.java.test.cmr.modules.depend.b/6.6.6 -> com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a/6.6.6"));
}finally{
server.stop(1);
}
if(carFileInCache.exists())
carFileInCache.delete();
server = startServer(port, repo, false, null, TimeoutIn.Head);
try{
// then try to compile only one module (the other being loaded from its car)
assertErrors(files, options, null,
new CompilerError(24, "cannot find module artifact com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a-6.6.6(.car|.jar)\n"+
" \t- dependency tree: com.redhat.ceylon.compiler.java.test.cmr.modules.depend.b/6.6.6 -> com.redhat.ceylon.compiler.java.test.cmr.modules.depend.a/6.6.6"));
}finally{
server.stop(1);
}
// now with put
server = startServer(port, repo, true, null, TimeoutIn.PutInitial);
try{
// then try to compile our module by outputting to HTTP
assertErrors("modules/single/module", Arrays.asList("-out", repoAURL, "-verbose:cmr", "-timeout", "1"), null,
new CompilerError(-1, "Failed to write module to repository: java.net.SocketTimeoutException: Timed out writing to "+repoAURL+"/com/redhat/ceylon/compiler/java/test/cmr/modules/single/6.6.6/com.redhat.ceylon.compiler.java.test.cmr.modules.single-6.6.6.src"));
}finally{
server.stop(1);
}
// now with put
server = startServer(port, repo, true, null, TimeoutIn.PutMiddle);
try{
// then try to compile our module by outputting to HTTP
assertErrors("modules/single/module", Arrays.asList("-out", repoAURL, "-verbose:cmr", "-timeout", "1"), null,
new CompilerError(-1, "Failed to write module to repository: java.net.SocketTimeoutException: Timed out writing to "+repoAURL+"/com/redhat/ceylon/compiler/java/test/cmr/modules/single/6.6.6/com.redhat.ceylon.compiler.java.test.cmr.modules.single-6.6.6.src"));
}finally{
server.stop(1);
}
}
@Test
public void testMdlHTTPOutputRepo() throws IOException{
testMdlHTTPOutputRepo(true, 42);
testMdlHTTPOutputRepo(false, 103);
}
private void testMdlHTTPOutputRepo(boolean herd, int requests) throws IOException{
RequestCounter rq = new RequestCounter();
// Compile the module in its own repo
File repo = makeRepo();
final int port = allocPortForTest();
final String repoAURL = getRepoUrl(port);
// now serve the first repo over HTTP
HttpServer server = startServer(port, repo, herd, rq);
try{
// then try to compile our module by outputting to HTTP
Boolean result = getCompilerTask(Arrays.asList("-out", repoAURL, "-verbose:cmr"), "modules/single/module.ceylon").call();
Assert.assertEquals(Boolean.TRUE, result);
}finally{
server.stop(1);
}
File carFile = getModuleArchive("com.redhat.ceylon.compiler.java.test.cmr.modules.single", "6.6.6", repo.getPath());
assertTrue(carFile.exists());
JarFile car = new JarFile(carFile);
// make sure it's not empty
ZipEntry moduleClass = car.getEntry("com/redhat/ceylon/compiler/java/test/cmr/modules/single/$module_.class");
assertNotNull(moduleClass);
car.close();
File srcFile = getSourceArchive("com.redhat.ceylon.compiler.java.test.cmr.modules.single", "6.6.6", repo.getPath());
assertTrue(srcFile.exists());
rq.check(requests);
}
@Test
public void testMdlHTTPMixedCompilation() throws IOException{
testMdlHTTPMixedCompilation(false, 199);
testMdlHTTPMixedCompilation(true, 86);
}
private void testMdlHTTPMixedCompilation(boolean herd, int requests) throws IOException{
RequestCounter rq = new RequestCounter();
// Compile the first module in its own repo
File repo = makeRepo();
final int port = allocPortForTest();
final String repoAURL = getRepoUrl(port);
// now serve the first repo over HTTP
HttpServer server = startServer(port, repo, herd, rq);
try{
// then try to compile our module by outputting to HTTP
Boolean result = getCompilerTask(Arrays.asList("-out", repoAURL, "-verbose:cmr"), "modules/mixed/JavaClass.java").call();
Assert.assertEquals(Boolean.TRUE, result);
result = getCompilerTask(Arrays.asList("-out", repoAURL, "-verbose:cmr"), "modules/mixed/CeylonClass.ceylon").call();
Assert.assertEquals(Boolean.TRUE, result);
}finally{
server.stop(1);
}
File carFile = getModuleArchive("com.redhat.ceylon.compiler.java.test.cmr.modules.mixed", "6.6.6", repo.getPath());
assertTrue(carFile.exists());
JarFile car = new JarFile(carFile);
// make sure it's not empty
ZipEntry entry = car.getEntry("com/redhat/ceylon/compiler/java/test/cmr/modules/mixed/$module_.class");
assertNotNull(entry);
entry = car.getEntry("com/redhat/ceylon/compiler/java/test/cmr/modules/mixed/CeylonClass.class");
assertNotNull(entry);
entry = car.getEntry("com/redhat/ceylon/compiler/java/test/cmr/modules/mixed/JavaClass.class");
assertNotNull(entry);
car.close();
File srcFile = getSourceArchive("com.redhat.ceylon.compiler.java.test.cmr.modules.mixed", "6.6.6", repo.getPath());
assertTrue(srcFile.exists());
JarFile src = new JarFile(srcFile);
// make sure it's not empty
entry = src.getEntry("com/redhat/ceylon/compiler/java/test/cmr/modules/mixed/module.ceylon");
assertNotNull(entry);
entry = src.getEntry("com/redhat/ceylon/compiler/java/test/cmr/modules/mixed/CeylonClass.ceylon");
assertNotNull(entry);
entry = src.getEntry("com/redhat/ceylon/compiler/java/test/cmr/modules/mixed/JavaClass.java");
assertNotNull(entry);
src.close();
rq.check(requests);
}
@Test
public void testMdlHTTPForbidden() throws IOException {
File repo = makeRepo();
final int port = allocPortForTest();
final String repoAURL = getRepoUrl(port);
HttpServer server = startServer(port, repo, true, null, HttpError.FORBIDDEN);
try{
// then try to compile our module by outputting to HTTP
assertErrors("modules/single/module", Arrays.asList("-out", repoAURL), null,
new CompilerError(-1, "Failed to write module to repository: authentication failed on repository "+repoAURL));
}finally{
server.stop(1);
}
}
}