/*
* Copyright 2017 ThoughtWorks, Inc.
*
* Licensed 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 com.thoughtworks.go.server.cache;
import java.io.File;
import java.util.ArrayList;
import com.thoughtworks.go.domain.JobIdentifier;
import com.thoughtworks.go.server.service.ArtifactsDirHolder;
import com.thoughtworks.go.server.web.ArtifactFolder;
import com.thoughtworks.go.util.ClassMockery;
import com.thoughtworks.go.util.FileUtil;
import com.thoughtworks.go.util.TestFileUtil;
import com.thoughtworks.go.util.ZipUtil;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.jmock.Expectations;
import org.jmock.integration.junit4.JMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static com.thoughtworks.go.matchers.FileExistsMatcher.exists;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@RunWith(JMock.class)
public class ZipArtifactCacheTest {
private static final JobIdentifier JOB_IDENTIFIER = new JobIdentifier("pipeline-name", "label-111", "stage-name", 1, "job-name", 666L);
private static final String JOB_FOLDERS = "pipelines/pipeline-name/label-111/stage-name/1/job-name/666";
ClassMockery context = new ClassMockery();
private ZipArtifactCache zipArtifactCache;
private File folder;
private ArtifactFolder artifactFolder;
private ArtifactsDirHolder artifactsDirHolder;
@Before public void setUp() throws Exception {
folder = TestFileUtil.createTempFolder("ZipArtifactCacheTest-" + System.currentTimeMillis());
File artifact = new File(folder, JOB_FOLDERS);
artifact.mkdirs();
TestFileUtil.createTestFolder(artifact, "dir");
TestFileUtil.createTestFile(artifact, "dir/file1");
artifactsDirHolder = context.mock(ArtifactsDirHolder.class);
context.checking(new Expectations() {{
allowing(artifactsDirHolder).getArtifactsDir();
will(returnValue(folder));
}});
zipArtifactCache = new ZipArtifactCache(this.artifactsDirHolder, new ZipUtil());
artifactFolder = new ArtifactFolder(JOB_IDENTIFIER, new File(artifact, "dir"), "dir");
}
@After public void tearDown() throws Exception {
FileUtil.deleteFolder(folder);
}
@Test public void shouldKnowWhenCacheAlreadyCreated() throws Exception {
zipArtifactCache.createCachedFile(artifactFolder);
assertThat(zipArtifactCache, cacheCreated(artifactFolder));
assertThat(zipArtifactCache.cachedFile(artifactFolder).getName(), is("dir.zip"));
}
@Test public void shouldCreateCacheWhenNotYetCreated() throws Exception {
waitForCacheCreated();
assertThat(zipArtifactCache, cacheCreated(artifactFolder));
File zipFile = zipArtifactCache.cachedFile(artifactFolder);
assertThat(zipFile.getAbsolutePath().replaceAll("\\\\", "/"), endsWith("cache/artifacts/" + JOB_FOLDERS + "/dir.zip"));
}
@Test public void shouldOnlyCreateCacheOnce() throws Exception {
ArrayList<FileCheckerThread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new FileCheckerThread());
}
for (FileCheckerThread thread : threads) { thread.start(); }
for (FileCheckerThread thread : threads) {
thread.join(1000);
if (thread.isAlive()) { fail("Timeout waiting for threads"); }
}
for (FileCheckerThread thread : threads) {
assertThat(thread.isDone(), is(true));
assertThat(thread.artifact.replaceAll("\\\\", "/"), endsWith(JOB_FOLDERS + "/dir.zip"));
}
}
@Test public void shouldRecoverFromOldZipTmpFile() throws Exception {
File cacheDir = new File(folder, "cache/artifacts/" + JOB_FOLDERS);
cacheDir.mkdirs();
TestFileUtil.createTestFile(cacheDir, "dir.zip.tmp");
waitForCacheCreated();
assertThat(new File(cacheDir, "dir.zip.tmp"), not(exists()));
new ZipUtil().unzip(new File(cacheDir, "dir.zip"), cacheDir);
assertThat(new File(cacheDir, "dir/file1"), exists());
}
private void waitForCacheCreated() throws Exception {
int timesTried = 10;
while (timesTried > 0 && !zipArtifactCache.cacheCreated(artifactFolder)) {
Thread.sleep(100);
timesTried--;
}
if (timesTried <= 0) { fail("Timeout creating cache"); }
}
private class FileCheckerThread extends Thread {
String artifact;
public boolean isDone() {
return artifact != null;
}
public void run() {
try {
while (!zipArtifactCache.cacheCreated(artifactFolder)) {
Thread.sleep(1);
}
artifact = zipArtifactCache.cachedFile(artifactFolder).getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
private TypeSafeMatcher<ZipArtifactCache> cacheCreated(final ArtifactFolder artifactFolder) {
return new TypeSafeMatcher<ZipArtifactCache>() {
public boolean matchesSafely(ZipArtifactCache item) {
try {
return item.cacheCreated(artifactFolder);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void describeTo(Description description) {
description.appendText("cacheCreated from " + artifactFolder);
}
};
}
}