package gov.loc.repository.bagit;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gov.loc.repository.bagit.conformance.BagLinter;
import gov.loc.repository.bagit.conformance.BagitWarning;
import gov.loc.repository.bagit.domain.Bag;
import gov.loc.repository.bagit.domain.Version;
import gov.loc.repository.bagit.exceptions.CorruptChecksumException;
import gov.loc.repository.bagit.exceptions.FileNotInPayloadDirectoryException;
import gov.loc.repository.bagit.exceptions.InvalidBagitFileFormatException;
import gov.loc.repository.bagit.exceptions.MaliciousPathException;
import gov.loc.repository.bagit.exceptions.MissingBagitFileException;
import gov.loc.repository.bagit.exceptions.MissingPayloadDirectoryException;
import gov.loc.repository.bagit.exceptions.MissingPayloadManifestException;
import gov.loc.repository.bagit.exceptions.UnparsableVersionException;
import gov.loc.repository.bagit.exceptions.UnsupportedAlgorithmException;
import gov.loc.repository.bagit.exceptions.VerificationException;
import gov.loc.repository.bagit.reader.BagReader;
import gov.loc.repository.bagit.verify.BagVerifier;
import gov.loc.repository.bagit.writer.BagWriter;
/**
* This class assumes that the compliance test suite repo has been cloned and is available locally
*/
public class BagitSuiteComplanceTest extends Assert {
private static final Logger logger = LoggerFactory.getLogger(BagitSuiteComplanceTest.class);
@Rule
public TemporaryFolder folder= new TemporaryFolder();
private static final Path complianceRepoRootDir = Paths.get("bagit-conformance-suite");
private static final BagTestCaseVistor visitor = new BagTestCaseVistor();
private static final BagReader reader = new BagReader();
private static final BagVerifier verifier = new BagVerifier();
@BeforeClass
public static void setupOnce() throws IOException{
if(!Files.exists(complianceRepoRootDir)){
throw new IOException("bagit-conformance-suite git repo was not found, did you clone it?");
}
Files.walkFileTree(complianceRepoRootDir, visitor);
}
@Test
public void testValidBags() throws Exception{
Bag bag;
for(final Path bagDir : visitor.getValidTestCases()){
bag = reader.read(bagDir);
verifier.isValid(bag, true);
}
}
@Test
public void testInvalidBags(){
int errorCount = 0;
Bag bag;
ConcurrentMap<Class<? extends Exception>, AtomicLong> map = new ConcurrentHashMap<>();
for(Path invalidBagDir : visitor.getInvalidTestCases()){
try{
bag = reader.read(invalidBagDir);
verifier.isValid(bag, true);
}catch(InvalidBagitFileFormatException | IOException | UnparsableVersionException |
MissingPayloadManifestException | MissingBagitFileException | MissingPayloadDirectoryException |
FileNotInPayloadDirectoryException | InterruptedException | MaliciousPathException |
CorruptChecksumException | VerificationException | UnsupportedAlgorithmException e){
logger.info("Found invalid os specific bag with message: {}", e.getMessage());
map.putIfAbsent(e.getClass(), new AtomicLong(0));
map.get(e.getClass()).incrementAndGet();
errorCount++;
}
}
assertEquals("every test case should throw an error", visitor.getInvalidTestCases().size(), errorCount);
logger.debug("Count of all errors found in generic invalid cases: {}", map);
}
@Test
public void testInvalidOperatingSystemSpecificBags(){
int errorCount = 0;
Bag bag;
List<Path> osSpecificInvalidPaths = visitor.getLinuxOnlyTestCases();
ConcurrentMap<Class<? extends Exception>, AtomicLong> map = new ConcurrentHashMap<>();
if(TestUtils.isExecutingOnWindows()){
osSpecificInvalidPaths = visitor.getWindowsOnlyTestCases();
}
for(Path invalidBagDir : osSpecificInvalidPaths){
try{
bag = reader.read(invalidBagDir);
verifier.isValid(bag, true);
}catch(InvalidBagitFileFormatException | IOException | UnparsableVersionException |
MissingPayloadManifestException | MissingBagitFileException | MissingPayloadDirectoryException |
FileNotInPayloadDirectoryException | InterruptedException | MaliciousPathException |
CorruptChecksumException | VerificationException | UnsupportedAlgorithmException e){
logger.info("Found invalid os specific bag with message: {}", e.getMessage());
map.putIfAbsent(e.getClass(), new AtomicLong(0));
map.get(e.getClass()).incrementAndGet();
errorCount++;
}
}
assertEquals("every test case should throw an error", osSpecificInvalidPaths.size(), errorCount);
logger.debug("Count of all errors found in os specific invalid cases: {}", map);
}
@Test
public void testWarnings() throws Exception{
Set<BagitWarning> warnings;
for(Path bagDir : visitor.getWarningTestCases()){
warnings = BagLinter.lintBag(bagDir);
assertTrue(warnings.size() > 0);
}
}
@Test
public void testReadWriteProducesSameBag() throws Exception{
Bag bag;
Path newBagDir;
for(final Path bagDir : visitor.getValidTestCases()){
newBagDir = folder.newFolder().toPath();
bag = reader.read(bagDir);
BagWriter.write(bag, newBagDir);
testTagFileContents(bag, newBagDir);
testBagsStructureAreEqual(bagDir, newBagDir);
}
}
private void testTagFileContents(final Bag originalBag, final Path newBagDir) throws IOException{
assertTrue("bagit.txt files differ",
compareFileContents(originalBag.getRootDir().resolve("bagit.txt"),
newBagDir.resolve("bagit.txt"), StandardCharsets.UTF_8));
if(originalBag.getVersion().isSameOrOlder(new Version(0, 95))){
assertTrue("package-info.txt files differ",
compareFileContents(originalBag.getRootDir().resolve("package-info.txt"),
newBagDir.resolve("package-info.txt"), originalBag.getFileEncoding()));
}
else{
if(Files.exists(originalBag.getRootDir().resolve("bag-info.txt"))){
assertTrue("bag-info.txt files differ",
compareFileContents(originalBag.getRootDir().resolve("bag-info.txt"),
newBagDir.resolve("bag-info.txt"), originalBag.getFileEncoding()));
}
}
}
//return true if the content is the same disregarding line endings
private final boolean compareFileContents(final Path file1, final Path file2, final Charset encoding) throws IOException {
List<String> lines1 = Files.readAllLines(file1, encoding);
List<String> lines2 = Files.readAllLines(file2, encoding);
for(int index=0; index<lines1.size(); index++){
String strippedLine1 = lines1.get(index).replaceAll("\\r|\\n", "");
String strippedLine2 = lines2.get(index).replaceAll("\\r|\\n", "");
if(!strippedLine1.equals(strippedLine2)){
return false;
}
}
return true;
}
private void testBagsStructureAreEqual(Path originalBag, Path newBag) throws IOException{
Files.walkFileTree(originalBag, new FileExistsVistor(originalBag, newBag));
}
}