/*******************************************************************************
* Copyright (c) 2011 Arapiki Solutions Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* "Peter Smith <psmith@arapiki.com>" - initial API and
* implementation and/or initial documentation
*******************************************************************************/
package com.buildml.utils.os;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.buildml.utils.os.FileSystemTraverseCallback;
import com.buildml.utils.os.ShellResult;
import com.buildml.utils.os.SystemUtils;
/**
* @author "Peter Smith <psmith@arapiki.com>"
*
*/
public class TestSystemUtils {
/** A temporary file that contains a simple executable program */
private static File ourTempExe;
/**
* A temporary directory that we'll fill up with interesting files and directories.
* This is used for testing the traverseFileSystem() method.
*/
private static File ourTempDir;
/**
* This method is called before any of test methods are called, but
* it's only called once per test suite.
* @throws java.lang.Exception
*/
@BeforeClass
public static void setUpBeforeClass() throws Exception {
/*
* Create a short perl script for test purposes. This code looks cryptic,
* but serves to exercise the stdin/stdout/stderr streams. This script takes
* two arguments:
* 1) The number of letters to write on the stdout, and equally, the number of
* digits to write on the stderr.
* 2) The exit code to terminate with.
* This script starts by echoing it's stdin to its stdout, then displays N letters/digits
* on the stdout/stderr respectively.
*/
ourTempExe = File.createTempFile("tempExecutable", null);
PrintStream tmpStream = new PrintStream(ourTempExe);
tmpStream.println("#!/usr/bin/perl");
tmpStream.println("while (<STDIN>) { print $_; };");
tmpStream.println("my $count = $ARGV[0];");
tmpStream.println("my $rc = $ARGV[1];");
tmpStream.println("for (my $i = 0; $i != $count; $i++) {");
tmpStream.println(" my $ch = 65 + ($i % 26);");
tmpStream.println(" my $num = ($i % 10);");
tmpStream.println(" print STDOUT chr($ch);");
tmpStream.println(" print STDERR $num;");
tmpStream.println("}");
tmpStream.println("exit($rc);");
tmpStream.close();
/* set execute permission - only works on Unix */
ourTempExe.setExecutable(true);
/*
* Next, create a temporary directory full of files and subdirectories, for the purpose of
* testing traverseFileSystem(). Note, we create a temporary directory name by first creating
* a temporary file, then stealing (reusing) the same name for a directory.
*/
ourTempDir = File.createTempFile("tempDir", null);
ourTempDir.delete();
ourTempDir.mkdir();
/* create some subdirectories */
File dirA = new File(ourTempDir.getPath() + "/dirA");
File dirB = new File(ourTempDir.getPath() + "/dirA/nested/dirB");
File dirC = new File(ourTempDir.getPath() + "/dirC");
dirA.mkdirs();
dirB.mkdirs();
dirC.mkdirs();
new File(ourTempDir + "/topLevelFile").createNewFile();
new File(dirA + "/fileInDirA").createNewFile();
new File(dirB + "/fileInDirB").createNewFile();
new File(dirB + "/anotherFileInDirB").createNewFile();
new File(dirB + "/aThirdFileInDirB").createNewFile();
new File(dirB + "/onelastFileInDirB").createNewFile();
}
/**
* This method is called once (and only once) when all test cases have completed.
* @throws java.lang.Exception
*/
@AfterClass
public static void tearDownAfterClass() throws Exception {
/* remove our short perl script */
ourTempExe.delete();
/* remove our temporary directory */
Runtime.getRuntime().exec("rm -r " + ourTempDir);
}
/*-------------------------------------------------------------------------------------*/
/**
* Test method for {@link com.buildml.utils.os.SystemUtils#executeShellCmd(java.lang.String[], java.lang.String)}.
* @throws InterruptedException
* @throws IOException
*/
@Test
public void testExecuteShellCmd() throws IOException, InterruptedException {
ShellResult sr;
/* Execute an invalid command - should return an IOException */
try {
sr = SystemUtils.executeShellCmd(new String[] {"/blah"}, "Hello World\n");
fail("Failed to throw IOException when executing invalid command");
} catch (IOException ex) {
/* passed */
}
/* request a specific error code - our perl script always does exit() with it's second argument */
sr = SystemUtils.executeShellCmd(new String[] {ourTempExe.toString(), "0", "23"}, "\n");
assertEquals(sr.getReturnCode(), 23);
/* Simply echo back our stdin - the stdout should be identical to the stdin we provided. */
sr = SystemUtils.executeShellCmd(new String[] {ourTempExe.toString(), "0", "0"},
"Hello World\n");
assertEquals(sr.getReturnCode(), 0);
assertEquals("Hello World\n", sr.getStdout());
assertEquals("", sr.getStderr());
/* Same, but with multiple lines of text. */
sr = SystemUtils.executeShellCmd(new String[] {ourTempExe.toString(), "0", "0"},
"Hello World\nHow are you?\n");
assertEquals(sr.getReturnCode(), 0);
assertEquals("Hello World\nHow are you?\n", sr.getStdout());
assertEquals("", sr.getStderr());
/*
* Now get the program to generate some of its own output - that is, 5 letters on stdout
* and 5 digits on stderr.
*/
sr = SystemUtils.executeShellCmd(new String[] {ourTempExe.toString(), "5", "0"}, "Hi\n");
assertEquals(sr.getReturnCode(), 0);
assertEquals("Hi\nABCDE\n", sr.getStdout());
assertEquals("01234\n", sr.getStderr());
/*
* Now try a really really big case, where the stdout and stderr will certainly be intermingled.
*/
int count = 250000;
sr = SystemUtils.executeShellCmd(new String[] {ourTempExe.toString(), String.valueOf(count)}, "");
assertEquals(sr.getReturnCode(), 0);
/* first, check the lengths */
String stdOut = sr.getStdout();
String stdErr = sr.getStderr();
assertEquals(count + 1, stdOut.length()); /* include the trailing \n */
assertEquals(count + 1, stdErr.length());
/* now check each individual character of what was returned */
for (int i = 0; i != count; i++) {
char ch = stdOut.charAt(i);
int num = (int)stdErr.charAt(i) - '0';
assertEquals('A' + (i % 26), ch);
assertEquals(i % 10, num);
}
/*
* Test the non-buffering variant. Even though there's output, we shouldn't get any
* of it.
*/
sr = SystemUtils.executeShellCmd(new String[] {ourTempExe.toString(), "0", "0"},
"Hello World\n", null, false, null);
assertEquals(sr.getReturnCode(), 0);
assertEquals("", sr.getStdout());
assertEquals("", sr.getStderr());
/*
* Test with an invalid working directory
*/
try {
sr = SystemUtils.executeShellCmd(new String[] {ourTempExe.toString(), "0", "0"},
"Hello World\n", null, false, new File("/invalid-path"));
fail("Failed to throw IOException when executing in invalid directory.");
} catch (IOException ex) {
/* passed */
}
/*
* Test with a valid working directory
*/
sr = SystemUtils.executeShellCmd(new String[] {ourTempExe.toString(), "0", "0"},
"Hello World\n", null, false, new File("/tmp"));
assertEquals(sr.getReturnCode(), 0);
assertEquals("", sr.getStdout());
assertEquals("", sr.getStderr());
/*
* Note: we can't automatically test the echoToOutput option, unless we had a way to
* observe our own standard output.
*/
}
/*-------------------------------------------------------------------------------------*/
/**
* Test class for capturing file system traversal callbacks. As we encounter names,
* gather them in a list, ready to be returned for later analysis.
*/
private class TestCallback extends FileSystemTraverseCallback {
/** For accumulating path names as we encounter them */
private ArrayList<String> names = new ArrayList<String>();
@Override
public void callback(File thisPath) {
names.add(thisPath.toString());
}
/**
* @return The names we've captured.
*/
public String [] getNames() {
String result[] = names.toArray(new String[0]);
names = new ArrayList<String>();
return result;
}
};
/*-------------------------------------------------------------------------------------*/
/**
* Test method for traverseFileSystem
* @throws Exception
*/
@Test
public void testTraverseFileSystem() throws Exception {
/*
* Create an object of type FileSystemTraverseCallback that'll be called for each
* file system path that's reported. Calling getNames() returns an array of Strings
* containing the file names.
*/
TestCallback callbackCollector = new TestCallback();
/* Test a non-existent directory */
SystemUtils.traverseFileSystem("/non-existent", SystemUtils.REPORT_DIRECTORIES | SystemUtils.REPORT_FILES,
callbackCollector);
String names[] = callbackCollector.getNames();
assertEquals(0, names.length);
/* report all directories in our test hierarchy */
SystemUtils.traverseFileSystem(ourTempDir.toString(), SystemUtils.REPORT_DIRECTORIES, callbackCollector);
names = callbackCollector.getNames();
assertEquals(5, names.length);
assertSortedPathArraysEqual(ourTempDir.toString(), names,
new String[] {"", "/dirA", "/dirA/nested", "/dirA/nested/dirB", "/dirC"});
/* report all files in our test hierarchy */
SystemUtils.traverseFileSystem(ourTempDir.toString(), SystemUtils.REPORT_FILES, callbackCollector);
names = callbackCollector.getNames();
assertSortedPathArraysEqual(ourTempDir.toString(), names,
new String[] {"/dirA/fileInDirA", "/dirA/nested/dirB/aThirdFileInDirB",
"/dirA/nested/dirB/anotherFileInDirB", "/dirA/nested/dirB/fileInDirB",
"/dirA/nested/dirB/onelastFileInDirB", "/topLevelFile"});
/* report all files and directories in our test hierarchy */
SystemUtils.traverseFileSystem(ourTempDir.toString(), SystemUtils.REPORT_FILES | SystemUtils.REPORT_DIRECTORIES,
callbackCollector);
names = callbackCollector.getNames();
assertSortedPathArraysEqual(ourTempDir.toString(), names,
new String[] {"", "/dirA", "/dirA/fileInDirA", "/dirA/nested", "/dirA/nested/dirB",
"/dirA/nested/dirB/aThirdFileInDirB",
"/dirA/nested/dirB/anotherFileInDirB", "/dirA/nested/dirB/fileInDirB",
"/dirA/nested/dirB/onelastFileInDirB", "/dirC", "/topLevelFile"});
/* filter out the "nested" directory */
SystemUtils.traverseFileSystem(ourTempDir.toString(),
null,
"nested",
SystemUtils.REPORT_FILES | SystemUtils.REPORT_DIRECTORIES,
callbackCollector);
names = callbackCollector.getNames();
assertSortedPathArraysEqual(ourTempDir.toString(), names,
new String[] {"", "/dirA", "/dirA/fileInDirA", "/dirC", "/topLevelFile"});
/* filter out the "nested" and "dirC" directories */
SystemUtils.traverseFileSystem(ourTempDir.toString(),
null,
"nested|dirC",
SystemUtils.REPORT_FILES | SystemUtils.REPORT_DIRECTORIES,
callbackCollector);
names = callbackCollector.getNames();
assertSortedPathArraysEqual(ourTempDir.toString(), names,
new String[] {"", "/dirA", "/dirA/fileInDirA", "/topLevelFile"});
/* only return the files that have "In" in their name */
SystemUtils.traverseFileSystem(ourTempDir.toString(),
".*In.*",
null,
SystemUtils.REPORT_FILES,
callbackCollector);
names = callbackCollector.getNames();
assertSortedPathArraysEqual(ourTempDir.toString(), names,
new String[] {"/dirA/fileInDirA", "/dirA/nested/dirB/aThirdFileInDirB",
"/dirA/nested/dirB/anotherFileInDirB", "/dirA/nested/dirB/fileInDirB",
"/dirA/nested/dirB/onelastFileInDirB"});
}
/*-------------------------------------------------------------------------------------*/
/**
* Helper method to determine whether two arrays of path names contain the same elements,
* even if those elements aren't in the same order in both arrays. Also, remove a prefix
* from each path in arr1 before comparing.
* @param arr1prefix The String prefix to remove
* @param arr1 The first array of Strings
* @param arr2 The second array of Strings
*/
public static void assertSortedPathArraysEqual(String arr1prefix, String[] arr1, String[] arr2) {
/* if one is null, then both must be null */
if (arr1 == null) {
if (arr2 != null) {
fail("One array is null, but the other isn't.");
} else {
/* everything is good */
return;
}
}
/* lengths must be the same */
if (arr1.length != arr2.length) {
fail("Arrays have different length: " + arr1.length + " versus " + arr2.length);
}
/* now sort the elements and compare them */
Arrays.sort(arr1);
Arrays.sort(arr2);
for (int i = 0; i < arr1.length; i++) {
String str1 = arr1[i];
if (!str1.startsWith(arr1prefix)) {
fail("Array element " + str1 + " doesn't start with " + arr1prefix);
}
str1 = str1.substring(arr1prefix.length());
String str2 = arr2[i];
//System.out.println("Comparing " + str1 + " and " + str2);
if (!str1.equals(str2)) {
fail("Mismatched array elements: " + str1 + " and " + str2);
}
}
}
/*-------------------------------------------------------------------------------------*/
/**
* Test for the SystemUtils.createTempDir() and SystemUtils.deleteDirectory() methods.
* @throws Exception
*/
@Test
public void testCreateDeleteTempDir() throws Exception {
/* create a temporary directory, making sure that it exists after it's created */
File dir1 = SystemUtils.createTempDir();
assertTrue(dir1.exists());
/* create a second temporary directory, making sure it's different from the first */
File dir2 = SystemUtils.createTempDir();
assertTrue(dir1.exists());
assertNotSame(dir1.toString(), dir2.toString());
/* store some additional files/directories in the first temp directory */
File subdir1 = new File(dir1, "subdir1");
File subdir2 = new File(dir1, "subdir2");
assertTrue(subdir1.mkdir());
assertTrue(subdir2.mkdir());
assertTrue(new File(dir1, "file1").createNewFile());
assertTrue(new File(dir1, "file2").createNewFile());
assertTrue(new File(subdir1, "file3").createNewFile());
assertTrue(new File(subdir1, "file4").createNewFile());
/* delete the second temp dir */
assertTrue(SystemUtils.deleteDirectory(dir2));
/* delete the first temp dir */
assertTrue(SystemUtils.deleteDirectory(dir1));
}
/*-------------------------------------------------------------------------------------*/
}