package cucumber.runtime.io;
import cucumber.runtime.Utils;
import gherkin.util.FixJava;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.webbitserver.HttpControl;
import org.webbitserver.HttpHandler;
import org.webbitserver.HttpRequest;
import org.webbitserver.HttpResponse;
import org.webbitserver.WebServer;
import org.webbitserver.netty.NettyWebServer;
import org.webbitserver.rest.Rest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class URLOutputStreamTest {
private WebServer webbit;
private final int threadsCount = 100;
private final long waitTimeoutMillis = 30000L;
private final List<File> tmpFiles = new ArrayList<File>();
private final List<String> threadErrors = new ArrayList<String>();
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
@Before
public void startWebbit() throws ExecutionException, InterruptedException {
webbit = new NettyWebServer(Executors.newSingleThreadExecutor(), new InetSocketAddress("127.0.0.1", 9873), URI.create("http://127.0.0.1:9873")).start().get();
}
@After
public void stopWebbit() throws ExecutionException, InterruptedException {
webbit.stop().get();
}
@Test
public void write_to_file_without_existing_parent_directory() throws IOException, URISyntaxException {
Path filesWithoutParent = Files.createTempDirectory("filesWithoutParent");
String baseURL = filesWithoutParent.toUri().toURL().toString();
URL urlWithoutParentDirectory = new URL(baseURL + "/non/existing/directory");
Writer w = new UTF8OutputStreamWriter(new URLOutputStream(urlWithoutParentDirectory));
w.write("Hellesøy");
w.close();
File testFile = new File(urlWithoutParentDirectory.toURI());
assertEquals("Hellesøy", FixJava.readReader(openUTF8FileReader(testFile)));
}
@Test
public void can_write_to_file() throws IOException {
File tmp = File.createTempFile("cucumber-jvm", "tmp");
Writer w = new UTF8OutputStreamWriter(new URLOutputStream(tmp.toURI().toURL()));
w.write("Hellesøy");
w.close();
assertEquals("Hellesøy", FixJava.readReader(openUTF8FileReader(tmp)));
}
@Test
public void can_http_put() throws IOException, ExecutionException, InterruptedException {
final BlockingQueue<String> data = new LinkedBlockingDeque<String>();
Rest r = new Rest(webbit);
r.PUT("/.cucumber/stepdefs.json", new HttpHandler() {
@Override
public void handleHttpRequest(HttpRequest req, HttpResponse res, HttpControl ctl) throws Exception {
data.offer(req.body());
res.end();
}
});
Writer w = new UTF8OutputStreamWriter(new URLOutputStream(new URL(Utils.toURL("http://localhost:9873/.cucumber"), "stepdefs.json")));
w.write("Hellesøy");
w.flush();
w.close();
assertEquals("Hellesøy", data.poll(1000, TimeUnit.MILLISECONDS));
}
@Test
public void throws_fnfe_if_http_response_is_404() throws IOException, ExecutionException, InterruptedException {
Writer w = new UTF8OutputStreamWriter(new URLOutputStream(new URL(Utils.toURL("http://localhost:9873/.cucumber"), "stepdefs.json")));
w.write("Hellesøy");
w.flush();
try {
w.close();
fail();
} catch (FileNotFoundException expected) {
}
}
@Test
public void throws_ioe_if_http_response_is_500() throws IOException, ExecutionException, InterruptedException {
Rest r = new Rest(webbit);
r.PUT("/.cucumber/stepdefs.json", new HttpHandler() {
@Override
public void handleHttpRequest(HttpRequest req, HttpResponse res, HttpControl ctl) throws Exception {
res.status(500);
res.content("something went wrong");
res.end();
}
});
Writer w = new UTF8OutputStreamWriter(new URLOutputStream(new URL(Utils.toURL("http://localhost:9873/.cucumber"), "stepdefs.json")));
w.write("Hellesøy");
w.flush();
try {
w.close();
fail();
} catch (IOException expected) {
assertEquals("PUT http://localhost:9873/.cucumber/stepdefs.json\n" +
"HTTP 500\nsomething went wrong", expected.getMessage());
}
}
@Test
public void do_not_throw_ioe_if_parent_dir_created_by_another_thread() {
final CountDownLatch countDownLatch = new CountDownLatch(1);
List<Thread> testThreads = getThreadsWithLatchForFile(countDownLatch, threadsCount);
startThreadsFromList(testThreads);
countDownLatch.countDown();
waitAllThreadsFromList(testThreads);
assertTrue("Not all parent folders were created for tmp file or tmp file was not created", isAllFilesCreated());
assertTrue("Some thread get error during work. Error list:" + threadErrors.toString(), threadErrors.isEmpty());
}
private Reader openUTF8FileReader(final File file) throws IOException {
return new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"));
}
private List<Thread> getThreadsWithLatchForFile(final CountDownLatch countDownLatch, int threadsCount) {
List<Thread> result = new ArrayList<Thread>();
String ballast = "" + System.currentTimeMillis();
for (int i = 0; i < threadsCount; i++) {
final int curThreadNo = i;
// It useful when 2-3 threads (not more) tries to create the same directory for the report
final File tmp = (i % 3 == 0 || i % 3 == 2) ?
new File(tempFolder.getRoot().getAbsolutePath() + "/cuce" + ballast + i + "/tmpFile.tmp") :
new File(tempFolder.getRoot().getAbsolutePath() + "/cuce" + ballast + (i - 1) + "/tmpFile.tmp");
tmpFiles.add(tmp);
result.add(new Thread() {
@Override
public void run() {
try {
// Every thread should wait command to run
countDownLatch.await();
new URLOutputStream(tmp.toURI().toURL());
} catch (IOException e) {
threadErrors.add("Thread" + curThreadNo + ": parent dir not created. " + e.getMessage());
} catch (InterruptedException e) {
threadErrors.add("Thread" + curThreadNo + ": not started on time. " + e.getMessage());
}
}
});
}
return result;
}
private void startThreadsFromList(List<Thread> threads) {
for (Thread thread : threads) {
thread.start();
}
}
private void waitAllThreadsFromList(List<Thread> threads) {
long timeStart = System.currentTimeMillis();
do {
// Protection from forever loop
if (System.currentTimeMillis() - timeStart > waitTimeoutMillis) {
assertTrue("Some threads are still alive", false);
}
} while (hasListAliveThreads(threads));
}
private boolean hasListAliveThreads(List<Thread> threads) {
for (Thread thread : threads) {
if (thread.isAlive()) {
return true;
}
}
return false;
}
private boolean isAllFilesCreated() {
for (File tmpFile : tmpFiles) {
if (tmpFile.getParentFile() == null || !tmpFile.getParentFile().isDirectory() || !tmpFile.exists()) {
return false;
}
}
return true;
}
}