Clean up the nresource patch & older code cache stuff.

Two important changes:

1. Don't change the name of the code cache dir as that would leave a stale code
cache around on old installations that were using the code cache.

2. Don't create a native libs cache dir if there are no native resources.

Everything else is mostly cosmetic and organizational.
This commit is contained in:
Michael Bayne
2018-12-06 11:44:29 -08:00
parent 20fd43fd98
commit 16b8b836e4
13 changed files with 239 additions and 281 deletions
@@ -1,8 +1,11 @@
package com.threerings.getdown.classpath.cache;
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.cache;
import java.io.File;
import java.io.FileFilter;
import com.threerings.getdown.util.FileUtil;
/**
@@ -44,38 +47,32 @@ public class GarbageCollector
/**
* Collect and delete garbage in the native cache. It tries to find a jar file with a matching
* lastmodified file, and deletes the entire directory accordingly.
* last modified file, and deletes the entire directory accordingly.
*/
public static void collectNative (File cacheDir, final long retentionPeriodMillis) {
FileUtil.walkDirectChildren(cacheDir, new FileUtil.Visitor() {
@Override
public void visit(File file) {
if (file.isDirectory()) {
// Get all the native jars in the directory (there should only be one though)
File[] nativejars = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getAbsolutePath().endsWith(".jar");
}
});
for (File nativejar : nativejars) {
if (nativejar==null || !nativejar.exists()) {
public static void collectNative (File cacheDir, final long retentionPeriodMillis)
{
File[] subdirs = cacheDir.listFiles();
if (subdirs != null) {
for (File dir : subdirs) {
if (dir.isDirectory()) {
// Get all the native jars in the directory (there should only be one)
for (File file : dir.listFiles()) {
if (!file.getName().endsWith(".jar")) {
continue;
}
File cachedFile = getCachedFile(nativejar);
File lastAccessedFile = getLastAccessedFile(nativejar);
if (!cachedFile.exists() || !lastAccessedFile.exists() || shouldDelete(lastAccessedFile, retentionPeriodMillis)) {
FileUtil.deleteDirHarder(file);
File cachedFile = getCachedFile(file);
File lastAccessedFile = getLastAccessedFile(file);
if (!cachedFile.exists() || !lastAccessedFile.exists() ||
shouldDelete(lastAccessedFile, retentionPeriodMillis)) {
FileUtil.deleteDirHarder(dir);
}
}
} else {
// @TODO There shouldn't be any loose files in native/ but if there are then what? Delete them?
// file.delete();
// @TODO There shouldn't be any loose files in native/ but if there are then
// what? Delete them? file.delete();
}
}
});
}
}
private static boolean shouldDelete (File lastAccessedFile, long retentionMillis)
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath.cache;
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.cache;
import java.io.File;
import java.io.IOException;
@@ -14,7 +19,6 @@ public class ResourceCache
public ResourceCache (File _cacheDir) throws IOException
{
this._cacheDir = _cacheDir;
createDirectoryIfNecessary(_cacheDir);
}
@@ -26,34 +30,16 @@ public class ResourceCache
}
/**
* Caches the given file under it's {@code digest}. Same as calling {@link #cacheFile(File, String, boolean)}
* with {@code false}
* @return the cached file
* Caches the given file under its {@code digest}.
* @param fileToCache file to cache.
* @param cacheSubdir the subdirectory of the cache directory in which to store the cached
* file. Usually either {@code digest} or a prefix of {@code digest}.
* @param digest a crypto digest of the cached files contents.
* @return the cached file.
*/
public File cacheFile (File fileToCache, String digest) throws IOException
public File cacheFile (File fileToCache, String cacheSubdir, String digest) throws IOException
{
return cacheFile(fileToCache, digest, false);
}
/**
* Caches the given file under it's {@code digest}.
* @param fileToCache file to cache
* @param digest used to determine the name of the directory to store file in
* @param useFullHashName if true, the name of the cache directory will not be truncated.
* This is useful if you need to avoid the possibility of two files
* sharing a directory.
* @return the cached file
*/
public File cacheFile (File fileToCache, String digest, boolean useFullHashName) throws IOException
{
File cacheLocation;
if (useFullHashName) {
cacheLocation = new File(_cacheDir, digest);
} else {
cacheLocation = new File(_cacheDir, digest.substring(0, 2));
}
File cacheLocation = new File(_cacheDir, cacheSubdir);
createDirectoryIfNecessary(cacheLocation);
File cachedFile = new File(cacheLocation, digest + getFileSuffix(fileToCache));
@@ -1,72 +0,0 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.classpath;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.concurrent.TimeUnit;
import com.threerings.getdown.classpath.cache.GarbageCollector;
import com.threerings.getdown.classpath.cache.ResourceCache;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
public class ClassPaths
{
/**
* Builds either a default or cached classpath based on {@code app}'s configuration.
*/
public static ClassPath buildClassPath (Application app) throws IOException
{
return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app);
}
/**
* Builds a {@link ClassPath} instance for {@code app} using the code resources in place in
* the app directory.
*/
public static ClassPath buildDefaultClassPath (Application app)
{
LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
for (Resource resource: app.getActiveCodeResources()) {
classPathEntries.add(resource.getFinalTarget());
}
return new ClassPath(classPathEntries);
}
/**
* Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into
* a cache directory and then referencing them from there. This avoids problems with
* overwriting in-use classpath elements when the application is later updated. This also
* "garbage collects" expired caches if necessary.
*/
public static ClassPath buildCachedClassPath (Application app) throws IOException
{
File codeCacheDir = new File(app.getAppDir(), Application.CACHE_DIR + "/code");
// a negative value of code_cache_retention_days allows to clean up the cache forcefully
if (app.getCodeCacheRetentionDays() != 0) {
runGarbageCollection(app, codeCacheDir);
}
ResourceCache cache = new ResourceCache(codeCacheDir);
LinkedHashSet<File> classPathEntries = new LinkedHashSet<>();
for (Resource resource: app.getActiveCodeResources()) {
File entry = cache.cacheFile(resource.getFinalTarget(), app.getDigest(resource));
classPathEntries.add(entry);
}
return new ClassPath(classPathEntries);
}
private static void runGarbageCollection (Application app, File cacheDir)
{
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
GarbageCollector.collect(cacheDir, retainMillis);
}
}
@@ -1,83 +0,0 @@
package com.threerings.getdown.classpath;
import com.threerings.getdown.classpath.cache.GarbageCollector;
import com.threerings.getdown.classpath.cache.ResourceCache;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.FileUtil;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile;
import static com.threerings.getdown.Log.log;
/**
* Similar to {@link ClassPath}, except used to represent directories containing native libraries instead.
*/
public class NativeLibPath extends ClassPath {
public NativeLibPath(LinkedHashSet<File> entries) {
super(entries);
}
/**
* Builds a {@link NativeLibPath} instance by first caching all native jars (indicated by
* nresource=[native jar]), unpacking them, and referencing the locations of each of the unpacked files.
* Also performs garbage collection similar to {@link ClassPaths#buildCachedClassPath}
*
* @param app used to determine native jars and related information
* @param addCurrentLibraryPath if true, it adds the locations referenced by
* {@code System.getProperty("java.library.path")} as well
*/
public static NativeLibPath buildLibsPath(Application app, boolean addCurrentLibraryPath) throws IOException {
List<Resource> resources = app.getNativeJars();
String[] curPaths = System.getProperty("java.library.path").split(File.pathSeparator);
LinkedHashSet<File> nativedirs = new LinkedHashSet<>();
File nativeCacheDir = new File(app.getAppDir(), Application.CACHE_DIR + "/native");
ResourceCache cache = new ResourceCache(nativeCacheDir);
// negative value forces total garbage collection, 0 avoids garbage collection at all
if (app.getCodeCacheRetentionDays() != 0) {
runGarbageCollection(app, nativeCacheDir);
}
for (Resource resource : resources) {
// Use untruncated directory names because in the off chance that two native jars share a directory AND
// contain files with the same names, we'll get overwriting issues when unpacking
File cachedFile = cache.cacheFile(resource.getFinalTarget(), app.getDigest(resource), true);
if (!getUnpackedIndicator(cachedFile).exists()) {
try {
FileUtil.unpackJar(new JarFile(cachedFile), cachedFile.getParentFile(), false);
getUnpackedIndicator(cachedFile).createNewFile();
} catch (IOException ioe) {
log.warning("Failed to unpack native jar", "File", cachedFile.getAbsolutePath(), ioe);
// Keep going and unpack the other jars...
}
}
nativedirs.add(cachedFile.getParentFile());
}
if (addCurrentLibraryPath) {
for (String path : curPaths) {
nativedirs.add(new File(path));
}
}
return new NativeLibPath(nativedirs);
}
private static void runGarbageCollection(Application app, File nativeCacheDir) {
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
GarbageCollector.collectNative(nativeCacheDir, retainMillis);
}
private static File getUnpackedIndicator(File cachedFile) {
return new File(cachedFile.getParent(), cachedFile.getName() + ".unpacked");
}
}
@@ -23,9 +23,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import com.threerings.getdown.classpath.ClassPaths;
import com.threerings.getdown.classpath.ClassPath;
import com.threerings.getdown.classpath.NativeLibPath;
import com.threerings.getdown.util.*;
// avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+
import com.threerings.getdown.util.Base64;
@@ -55,9 +52,6 @@ public class Application
/** A special classname that means 'use -jar code.jar' instead of a classname. */
public static final String MANIFEST_CLASS = "manifest";
/** Name of directory to store cached files in. */
public static final String CACHE_DIR = ".cache";
/** Used to communicate information about the UI displayed when updating the application. */
public static final class UpdateInterface
{
@@ -379,19 +373,17 @@ public class Application
}
/**
* Returns all jar resources indicated to contain native library files (.dll, .so etc.)
* Returns all resources indicated to contain native library files (.dll, .so, etc.).
*/
public List<Resource> getNativeJars() {
List<Resource> nativeJars = new ArrayList<>();
public List<Resource> getNativeResources ()
{
List<Resource> natives = new ArrayList<>();
for (Resource resource: _resources) {
if (resource.isNativeJar()) {
nativeJars.add(resource);
if (resource.isNative()) {
natives.add(resource);
}
}
return nativeJars;
return natives;
}
/**
@@ -702,7 +694,10 @@ public class Application
parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
ArrayList<Resource> rsrcs = new ArrayList<>();
parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs);
parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs);
parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
parseResources(config, auxgroup + ".presource", Resource.PRELOAD, rsrcs);
parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs);
_auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
}
@@ -934,12 +929,7 @@ public class Application
boolean dashJarMode = MANIFEST_CLASS.equals(_class);
// add the -classpath arguments if we're not in -jar mode
ClassPath classPath = ClassPaths.buildClassPath(this);
// get the -Djava.library.path value to pass
// @TODO optional getdown.txt parameter to set addCurrentLibraryPath to true or false?
NativeLibPath nativeLibPaths = NativeLibPath.buildLibsPath(this, true);
ClassPath classPath = PathBuilder.buildClassPath(this);
if (!dashJarMode) {
args.add("-classpath");
args.add(classPath.asArgumentString());
@@ -963,8 +953,12 @@ public class Application
// add the marker indicating the app is running in getdown
args.add("-D" + Properties.GETDOWN + "=true");
// set the native library path
args.add("-Djava.library.path=" + nativeLibPaths.asArgumentString());
// set the native library path if we have native resources
// @TODO optional getdown.txt parameter to set addCurrentLibraryPath to true or false?
ClassPath javaLibPath = PathBuilder.buildLibsPath(this, true);
if (javaLibPath != null) {
args.add("-Djava.library.path=" + javaLibPath.asArgumentString());
}
// pass along any pass-through arguments
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
@@ -1044,7 +1038,7 @@ public class Application
*/
public void invokeDirect () throws IOException
{
ClassPath classPath = ClassPaths.buildClassPath(this);
ClassPath classPath = PathBuilder.buildClassPath(this);
URL[] jarUrls = classPath.asUrls();
// create custom class loader
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath;
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.data;
import java.io.File;
import java.net.MalformedURLException;
@@ -9,9 +14,9 @@ import java.util.LinkedHashSet;
import java.util.Set;
/**
* Represents the class path and it's elements of the application to be launched. The class path can
* either be represented as an {@link #asArgumentString() argument string} for the java command line
* or as an {@link #asUrls() array of URLs} to be used by a {@link URLClassLoader}.
* Represents the class path and it's elements of the application to be launched. The class path
* can either be represented as an {@link #asArgumentString() argument string} for the java command
* line or as an {@link #asUrls() array of URLs} to be used by a {@link URLClassLoader}.
*/
public class ClassPath
{
@@ -0,0 +1,135 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.data;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile;
import com.threerings.getdown.cache.GarbageCollector;
import com.threerings.getdown.cache.ResourceCache;
import com.threerings.getdown.util.FileUtil;
import static com.threerings.getdown.Log.log;
public class PathBuilder
{
/** Name of directory to store cached code files in. */
public static final String CODE_CACHE_DIR = ".cache";
/** Name of directory to store cached native resources in. */
public static final String NATIVE_CACHE_DIR = ".ncache";
/**
* Builds either a default or cached classpath based on {@code app}'s configuration.
*/
public static ClassPath buildClassPath (Application app) throws IOException
{
return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app);
}
/**
* Builds a {@link ClassPath} instance for {@code app} using the code resources in place in
* the app directory.
*/
public static ClassPath buildDefaultClassPath (Application app)
{
LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
for (Resource resource: app.getActiveCodeResources()) {
classPathEntries.add(resource.getFinalTarget());
}
return new ClassPath(classPathEntries);
}
/**
* Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into
* a cache directory and then referencing them from there. This avoids problems with
* overwriting in-use classpath elements when the application is later updated. This also
* "garbage collects" expired caches if necessary.
*/
public static ClassPath buildCachedClassPath (Application app) throws IOException
{
File codeCacheDir = new File(app.getAppDir(), CODE_CACHE_DIR);
// a negative value of code_cache_retention_days allows to clean up the cache forcefully
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
if (retainMillis != 0L) {
GarbageCollector.collect(codeCacheDir, retainMillis);
}
ResourceCache cache = new ResourceCache(codeCacheDir);
LinkedHashSet<File> classPathEntries = new LinkedHashSet<>();
for (Resource resource: app.getActiveCodeResources()) {
String digest = app.getDigest(resource);
File entry = cache.cacheFile(resource.getFinalTarget(), digest.substring(0, 2), digest);
classPathEntries.add(entry);
}
return new ClassPath(classPathEntries);
}
/**
* Builds a {@link ClassPath} instance by first caching all native jars (indicated by
* nresource=[native jar]), unpacking them, and referencing the locations of each of the
* unpacked files. Also performs garbage collection similar to {@link #buildCachedClassPath}
*
* @param app used to determine native jars and related information.
* @param addCurrentLibraryPath if true, it adds the locations referenced by
* {@code System.getProperty("java.library.path")} as well.
* @return a classpath instance if at least one native resource was found and unpacked,
* {@code null} if no native resources were used by the application.
*/
public static ClassPath buildLibsPath (Application app,
boolean addCurrentLibraryPath) throws IOException {
List<Resource> resources = app.getNativeResources();
if (resources.isEmpty()) {
return null;
}
LinkedHashSet<File> nativedirs = new LinkedHashSet<>();
File nativeCacheDir = new File(app.getAppDir(), NATIVE_CACHE_DIR);
ResourceCache cache = new ResourceCache(nativeCacheDir);
// negative value forces total garbage collection, 0 avoids garbage collection at all
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
if (retainMillis != 0L) {
GarbageCollector.collectNative(nativeCacheDir, retainMillis);
}
for (Resource resource : resources) {
// Use untruncated cache subdirectory names to avoid overwriting issues when unpacking,
// in the off chance that two native jars share a directory AND contain files with the
// same names
String digest = app.getDigest(resource);
File cachedFile = cache.cacheFile(resource.getFinalTarget(), digest, digest);
File cachedParent = cachedFile.getParentFile();
File unpackedIndicator = new File(cachedParent, cachedFile.getName() + ".unpacked");
if (!unpackedIndicator.exists()) {
try {
FileUtil.unpackJar(new JarFile(cachedFile), cachedParent, false);
unpackedIndicator.createNewFile();
} catch (IOException ioe) {
log.warning("Failed to unpack native jar",
"file", cachedFile.getAbsolutePath(), ioe);
// Keep going and unpack the other jars...
}
}
nativedirs.add(cachedFile.getParentFile());
}
if (addCurrentLibraryPath) {
for (String path : System.getProperty("java.library.path").split(File.pathSeparator)) {
nativedirs.add(new File(path));
}
}
return new ClassPath(nativedirs);
}
}
@@ -46,7 +46,7 @@ public class Resource implements Comparable<Resource>
public static final EnumSet<Attr> UNPACK = EnumSet.of(Attr.UNPACK);
public static final EnumSet<Attr> EXEC = EnumSet.of(Attr.EXEC);
public static final EnumSet<Attr> PRELOAD = EnumSet.of(Attr.PRELOAD);
public static final EnumSet<Attr> NATIVE = EnumSet.of(Attr.NATIVE);
public static final EnumSet<Attr> NATIVE = EnumSet.of(Attr.NATIVE);
/**
* Computes the MD5 hash of the supplied file.
@@ -218,12 +218,11 @@ public class Resource implements Comparable<Resource>
/**
* Returns true if this resource is a native lib jar.
*/
public boolean isNativeJar ()
public boolean isNative ()
{
return _attrs.contains(Attr.NATIVE);
}
/**
* Computes the MD5 hash of this resource's underlying file.
* <em>Note:</em> This is both CPU and I/O intensive.
@@ -79,20 +79,17 @@ public class FileUtil
/**
* Force deletes {@code file} and all of its children recursively using {@link #deleteHarder}.
* Note that some children may still be deleted even if {@code false} is returned. Also, since {@link #deleteHarder}
* is used, the {@code file} could be deleted once the jvm exits even if {@code false} is returned.
* Note that some children may still be deleted even if {@code false} is returned. Also, since
* {@link #deleteHarder} is used, the {@code file} could be deleted once the jvm exits even if
* {@code false} is returned.
*
* @param file file to delete
* @param file file to delete.
* @return true iff {@code file} was successfully deleted.
*/
public static boolean deleteDirHarder(File file) {
public static boolean deleteDirHarder (File file) {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
if (child.isDirectory()) {
deleteDirHarder(child);
} else {
deleteHarder(child);
}
deleteDirHarder(child);
}
}
return deleteHarder(file);
@@ -211,7 +208,7 @@ public class FileUtil
}
/**
* Used by {@link #walkTree} and {@link #walkDirectChildren}.
* Used by {@link #walkTree}.
*/
public interface Visitor
{
@@ -239,21 +236,4 @@ public class FileUtil
}
}
}
/**
* Walks all direct sub-files of {@code root}, calling {@code visitor} on each file.
*/
public static void walkDirectChildren (final File root, Visitor visitor) {
File[] children = root.listFiles();
if (children == null) return;
Deque<File> stack = new ArrayDeque<>(Arrays.asList(children));
while (!stack.isEmpty()) {
File currentFile = stack.pop();
if (currentFile.exists()) {
visitor.visit(currentFile);
}
}
}
}
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath.cache;
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.cache;
import java.io.File;
import java.io.IOException;
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath.cache;
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.cache;
import java.io.File;
import java.io.IOException;
@@ -26,7 +31,7 @@ public class ResourceCacheTest
private File cacheFile() throws IOException
{
return _cache.cacheFile(_fileToCache, "abc123");
return _cache.cacheFile(_fileToCache, "abc123", "abc123");
}
@Test public void shouldTrackFileUsage () throws IOException
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath;
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.data;
import java.io.File;
import java.io.IOException;
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath;
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.data;
import java.io.File;
import java.io.IOException;
@@ -14,11 +19,8 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
@RunWith(MockitoJUnitRunner.class)
public class ClassPathsTest
public class PathBuilderTest
{
@Before public void setupFilesAndResources () throws IOException
{
@@ -33,7 +35,7 @@ public class ClassPathsTest
@Test public void shouldBuildDefaultClassPath () throws IOException
{
ClassPath classPath = ClassPaths.buildDefaultClassPath(_application);
ClassPath classPath = PathBuilder.buildDefaultClassPath(_application);
String expectedClassPath = _firstJarFile.getAbsolutePath() + File.pathSeparator +
_secondJarFile.getAbsolutePath();
assertEquals(expectedClassPath, classPath.asArgumentString());
@@ -46,15 +48,15 @@ public class ClassPathsTest
when(_application.getCodeCacheRetentionDays()).thenReturn(1);
Path firstCachedJarFile = _appdir.getRoot().toPath().
resolve(Application.CACHE_DIR + "/code").resolve("fi").resolve("first.jar");
resolve(PathBuilder.CODE_CACHE_DIR).resolve("fi").resolve("first.jar");
Path secondCachedJarFile = _appdir.getRoot().toPath().
resolve(Application.CACHE_DIR + "/code").resolve("se").resolve("second.jar");
resolve(PathBuilder.CODE_CACHE_DIR).resolve("se").resolve("second.jar");
String expectedClassPath = firstCachedJarFile.toAbsolutePath() + File.pathSeparator +
secondCachedJarFile.toAbsolutePath();
ClassPath classPath = ClassPaths.buildCachedClassPath(_application);
ClassPath classPath = PathBuilder.buildCachedClassPath(_application);
assertEquals(expectedClassPath, classPath.asArgumentString());
}