/*******************************************************************************
* 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.scanner.legacy;
import static org.junit.Assert.*;
import java.io.File;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.buildml.model.CommonTestUtils;
import com.buildml.model.IActionMgr;
import com.buildml.model.IActionMgr.OperationType;
import com.buildml.model.IBuildStore;
import com.buildml.model.IFileMgr;
import com.buildml.model.IFileMgr.PathType;
import com.buildml.utils.os.SystemUtils;
/**
* Basic testing that the LegacyBuildScanner can produce a valid
* BuildStore. There are many test cases, split over multiple
* test case files, with this file testing C Functions that
* perform an link/unlink/rename()-like operation.
*
* @author "Peter Smith <psmith@arapiki.com>"
*/
public class TestCFuncLink {
/* variables used in many test cases */
private IBuildStore bs = null;
private IActionMgr actionMgr = null;
private IFileMgr fileMgr = null;
private int rootAction;
private int action;
private Integer fileAccesses[], fileReads[], fileWrites[], fileModifies[], fileDeletes[];
/** temporary directory into which test cases can store files */
private File tmpDir;
/*-------------------------------------------------------------------------------------*/
/**
* Called before each test case starts. Creates a temporary directory in which the
* test case can store temporary files.
* @throws Exception
*/
@Before
public void setUp() throws Exception {
tmpDir = SystemUtils.createTempDir();
}
/*-------------------------------------------------------------------------------------*/
/**
* Called after each test case ends. Removes the temporary directory and its content.
* @throws Exception
*/
@After
public void tearDown() throws Exception {
SystemUtils.deleteDirectory(tmpDir);
}
/*-------------------------------------------------------------------------------------*/
/**
* Given the source code of a small C program, compile the program and scan it into a
* BuildStore. We then retrieve the one (and only) action that was registered in the
* BuildStore, along with the lists of files that were accessed (accessed, read, written,
* and deleted).
* @param programCode The body of the small C program to be compiled.
* @param args The command line arguments to pass to the small C program.
* @throws Exception Something went wrong when compiling/running the program.
*/
private void traceOneProgram(String programCode, String args[]) throws Exception {
/* compile, run, and trace the program */
bs = BuildScannersCommonTestUtils.parseLegacyProgram(tmpDir, programCode, args);
/* fetch references to sub objects */
actionMgr = bs.getActionMgr();
fileMgr = bs.getFileMgr();
/* find the root action */
rootAction = actionMgr.getRootAction("root");
/* there should only be one child action */
Integer childActions[] = actionMgr.getChildren(rootAction);
assertEquals(1, childActions.length);
/* this is the action ID of the one action */
action = childActions[0];
/* fetch the file access arrays */
fileAccesses = actionMgr.getFilesAccessed(action, OperationType.OP_UNSPECIFIED);
fileReads = actionMgr.getFilesAccessed(action, OperationType.OP_READ);
fileWrites = actionMgr.getFilesAccessed(action, OperationType.OP_WRITE);
fileModifies = actionMgr.getFilesAccessed(action, OperationType.OP_MODIFIED);
fileDeletes = actionMgr.getFilesAccessed(action, OperationType.OP_DELETE);
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the link() C function.
* @throws Exception
*/
@Test
public void testLink() throws Exception {
/*
* create a hard link to a valid file
*/
assertTrue(new File(tmpDir, "oldFile1").createNewFile());
traceOneProgram(
"#include <unistd.h>\n" +
"int main() {" +
" chdir(\"" + tmpDir + "\");" +
" link(\"oldFile1\", \"linkFile1\");" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileReads.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/oldFile1", fileMgr.getPathName(fileReads[0]));
assertEquals(tmpDir + "/linkFile1", fileMgr.getPathName(fileWrites[0]));
/*
* Creation of a hard link to a non-existent file should not be logged.
*/
traceOneProgram(
"#include <unistd.h>\n" +
"int main() {" +
" chdir(\"" + tmpDir + "\");" +
" link(\"badFile\", \"linkFile1a\");" +
" return 0;" +
"}", null);
assertEquals(0, fileAccesses.length);
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the linkat() C function.
* @throws Exception
*/
@Test
public void testLinkat() throws Exception {
/*
* create a hard link to a valid file
*/
assertTrue(new File(tmpDir, "oldFile2").createNewFile());
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" linkat(dirfd, \"oldFile2\", dirfd, \"linkFile2\", 0);" +
" return 0;" +
"}", null);
assertEquals(3, fileAccesses.length); /* include the dirfd open */
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/linkFile2", fileMgr.getPathName(fileWrites[0]));
assertTrue(CommonTestUtils.sortedArraysEqual(fileReads,
new Integer[] { fileMgr.getPath(tmpDir + "/oldFile2"),
fileMgr.getPath(tmpDir.toString()) }));
/*
* Creation of a hard link to a non-existent file should not be logged.
*/
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" linkat(dirfd, \"badFile2\", dirfd, \"linkFile2a\", 0);" +
" return 0;" +
"}", null);
assertEquals(1, fileAccesses.length); /* only the dirfd open is read */
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the remove() C function.
* @throws Exception
*/
@Test
public void testRemove() throws Exception {
/*
* Remove a file that exists.
*/
assertTrue(new File(tmpDir, "fileToDelete").createNewFile());
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" remove(\"" + tmpDir + "/fileToDelete\");" +
" return 0;" +
"}", null);
assertEquals(1, fileAccesses.length);
assertEquals(1, fileDeletes.length);
assertEquals(tmpDir + "/fileToDelete", fileMgr.getPathName(fileDeletes[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileDeletes[0]));
/*
* Remove a directory that exists
*/
assertTrue(new File(tmpDir, "dirToDelete").mkdirs());
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" remove(\"" + tmpDir + "/dirToDelete\");" +
" return 0;" +
"}", null);
assertEquals(1, fileAccesses.length);
assertEquals(1, fileDeletes.length);
assertEquals(tmpDir + "/dirToDelete", fileMgr.getPathName(fileDeletes[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileDeletes[0]));
/*
* Remove a file that doesn't exist
*/
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" remove(\"" + tmpDir + "/invalidFile\");" +
" return 0;" +
"}", null);
assertEquals(0, fileAccesses.length);
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the rename() C function.
* @throws Exception
*/
@Test
public void testRename() throws Exception {
/*
* Rename a file that exists.
*/
assertTrue(new File(tmpDir, "fileToRename").createNewFile());
traceOneProgram(
"#include <stdio.h>\n" +
"int main() {" +
" rename(\"" + tmpDir + "/fileToRename\", \"" + tmpDir + "/newName\");" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileDeletes.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/fileToRename", fileMgr.getPathName(fileDeletes[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileDeletes[0]));
assertEquals(tmpDir + "/newName", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileWrites[0]));
/*
* Rename a directory that exists
*/
assertTrue(new File(tmpDir, "dirToRename").mkdirs());
traceOneProgram(
"#include <stdio.h>\n" +
"int main() {" +
" rename(\"" + tmpDir + "/dirToRename\", \"" + tmpDir + "/newDirName\");" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileDeletes.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/dirToRename", fileMgr.getPathName(fileDeletes[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileDeletes[0]));
assertEquals(tmpDir + "/newDirName", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileWrites[0]));
/*
* Rename a file that doesn't exist
*/
traceOneProgram(
"#include <stdio.h>\n" +
"int main() {" +
" rename(\"" + tmpDir + "/badFile\", \"" + tmpDir + "/newBadFile\");" +
" return 0;" +
"}", null);
assertEquals(0, fileAccesses.length);
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the renameat() C function.
* @throws Exception
*/
@Test
public void testRenameat() throws Exception {
/*
* Rename a file that exists.
*/
assertTrue(new File(tmpDir, "fileToRename").createNewFile());
traceOneProgram(
"#include <stdio.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" renameat(dirfd, \"fileToRename\", dirfd, \"newName\");" +
" return 0;" +
"}", null);
assertEquals(3, fileAccesses.length);
assertEquals(1, fileReads.length); /* opening the dirfd */
assertEquals(1, fileDeletes.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/fileToRename", fileMgr.getPathName(fileDeletes[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileDeletes[0]));
assertEquals(tmpDir + "/newName", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileWrites[0]));
/*
* Rename a directory that exists
*/
assertTrue(new File(tmpDir, "dirToRename").mkdirs());
traceOneProgram(
"#include <stdio.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" renameat(dirfd, \"dirToRename\", dirfd, \"newDirName\");" +
" return 0;" +
"}", null);
assertEquals(3, fileAccesses.length);
assertEquals(1, fileReads.length); /* opening the dirfd */
assertEquals(1, fileDeletes.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/dirToRename", fileMgr.getPathName(fileDeletes[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileDeletes[0]));
assertEquals(tmpDir + "/newDirName", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileWrites[0]));
/*
* Rename a file that doesn't exist
*/
traceOneProgram(
"#include <stdio.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" renameat(dirfd, \"badFile\", dirfd, \"newBadName\");" +
" return 0;" +
"}", null);
assertEquals(1, fileAccesses.length); /* the dirfd open */
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the symlink() C function.
* @throws Exception
*/
@Test
public void testSymlink() throws Exception {
/*
* create a symbolic link to a valid file.
*/
assertTrue(new File(tmpDir, "oldFile1").createNewFile());
traceOneProgram(
"#include <unistd.h>\n" +
"int main() {" +
" chdir(\"" + tmpDir + "\");" +
" symlink(\"oldFile1\", \"linkFile1\");" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileReads.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/oldFile1", fileMgr.getPathName(fileReads[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileReads[0]));
assertEquals(tmpDir + "/linkFile1", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileWrites[0]));
/*
* create a symbolic link to a valid directory.
*/
assertTrue(new File(tmpDir, "oldDir1").mkdirs());
traceOneProgram(
"#include <unistd.h>\n" +
"int main() {" +
" chdir(\"" + tmpDir + "\");" +
" symlink(\"oldDir1\", \"linkDir1\");" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileReads.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/oldDir1", fileMgr.getPathName(fileReads[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileReads[0]));
assertEquals(tmpDir + "/linkDir1", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileWrites[0]));
/*
* Creation of a symlink to a non-existent file should still work
* (as opposed to hard link, which would fail).
*/
traceOneProgram(
"#include <unistd.h>\n" +
"int main() {" +
" chdir(\"" + tmpDir + "\");" +
" symlink(\"badFile\", \"linkFile1a\");" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileReads.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/badFile", fileMgr.getPathName(fileReads[0]));
assertEquals(tmpDir + "/linkFile1a", fileMgr.getPathName(fileWrites[0]));
/*
* create a symbolic link to a valid file, using a relative path. Make
* sure that the absolute path of the target file is relative to the
* source file's directory, not to the current directory.
*/
assertTrue(new File(tmpDir, "subdir1/subdir2").mkdirs());
traceOneProgram(
"#include <unistd.h>\n" +
"int main() {" +
" chdir(\"" + tmpDir + "\");" +
" symlink(\"../../oldFile1\", \"subdir1/subdir2/linkFile1\");" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileReads.length);
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/oldFile1", fileMgr.getPathName(fileReads[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileReads[0]));
assertEquals(tmpDir + "/subdir1/subdir2/linkFile1", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileWrites[0]));
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the symlinkat() C function.
* @throws Exception
*/
@Test
public void testSymlinkat() throws Exception {
/*
* create a symbolic link to a valid file.
*/
assertTrue(new File(tmpDir, "oldFile2").createNewFile());
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" symlinkat(\"" + tmpDir + "/oldFile2\", dirfd, \"linkFile2\");" +
" return 0;" +
"}", null);
assertEquals(3, fileAccesses.length); /* include the dirfd open */
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/linkFile2", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileWrites[0]));
assertTrue(CommonTestUtils.sortedArraysEqual(fileReads,
new Integer[] { fileMgr.getPath(tmpDir + "/oldFile2"),
fileMgr.getPath(tmpDir.toString()) }));
/*
* create a symbolic link to a valid directory
*/
assertTrue(new File(tmpDir, "oldDir2").mkdirs());
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" symlinkat(\"" + tmpDir + "/oldDir2\", dirfd, \"linkDir2\");" +
" return 0;" +
"}", null);
assertEquals(3, fileAccesses.length); /* include the dirfd open */
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/linkDir2", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileWrites[0]));
assertTrue(CommonTestUtils.sortedArraysEqual(fileReads,
new Integer[] { fileMgr.getPath(tmpDir + "/oldDir2"),
fileMgr.getPath(tmpDir.toString()) }));
/*
* Creation of a symbolic link to a non-existent file should still work.
*/
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" symlinkat(\"" + tmpDir + "/badFile2\", dirfd, \"linkFile2a\");" +
" return 0;" +
"}", null);
assertEquals(3, fileAccesses.length); /* include the dirfd open */
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/linkFile2a", fileMgr.getPathName(fileWrites[0]));
assertTrue(CommonTestUtils.sortedArraysEqual(fileReads,
new Integer[] { fileMgr.getPath(tmpDir + "/badFile2"),
fileMgr.getPath(tmpDir.toString()) }));
/*
* create a symbolic link to a valid file where the target file is
* relative to the source file's directory, rather than the current
* directory.
*/
assertTrue(new File(tmpDir, "subdir1/subdir2").mkdirs());
traceOneProgram(
"#include <unistd.h>\n" +
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" symlinkat(\"../../oldFile2\", dirfd, \"subdir1/subdir2/linkFile2\");" +
" return 0;" +
"}", null);
assertEquals(3, fileAccesses.length); /* include the dirfd open */
assertEquals(1, fileWrites.length);
assertEquals(tmpDir + "/subdir1/subdir2/linkFile2", fileMgr.getPathName(fileWrites[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileWrites[0]));
assertTrue(CommonTestUtils.sortedArraysEqual(fileReads,
new Integer[] { fileMgr.getPath(tmpDir + "/oldFile2"),
fileMgr.getPath(tmpDir.toString()) }));
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the unlink() C function.
* @throws Exception
*/
@Test
public void testUnlink() throws Exception {
/*
* Unlink a valid file.
*/
assertTrue(new File(tmpDir, "fileToDelete").createNewFile());
traceOneProgram(
"#include <unistd.h>\n" +
"int main() {" +
" unlink(\"" + tmpDir + "/fileToDelete\");" +
" return 0;" +
"}", null);
assertEquals(1, fileAccesses.length);
assertEquals(1, fileDeletes.length);
assertEquals(tmpDir + "/fileToDelete", fileMgr.getPathName(fileDeletes[0]));
/*
* Unlink a file that doesn't exist.
*/
traceOneProgram(
"#include <unistd.h>\n" +
"int main() {" +
" unlink(\"" + tmpDir + "/invalidFile\");" +
" return 0;" +
"}", null);
assertEquals(0, fileAccesses.length);
}
/*-------------------------------------------------------------------------------------*/
/**
* Test the unlinkat() C function.
* @throws Exception
*/
@Test
public void testUnlinkat() throws Exception {
/*
* Unlinkat a valid file.
*/
assertTrue(new File(tmpDir, "fileToDelete").createNewFile());
traceOneProgram(
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" unlinkat(dirfd, \"fileToDelete\", 0);" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileDeletes.length);
assertEquals(tmpDir + "/fileToDelete", fileMgr.getPathName(fileDeletes[0]));
assertEquals(PathType.TYPE_FILE, fileMgr.getPathType(fileDeletes[0]));
/*
* Unlinkat a valid directory (using the AT_REMOVEDIR flag).
*/
assertTrue(new File(tmpDir, "dirToDelete").mkdirs());
traceOneProgram(
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" unlinkat(dirfd, \"dirToDelete\", AT_REMOVEDIR);" +
" return 0;" +
"}", null);
assertEquals(2, fileAccesses.length);
assertEquals(1, fileDeletes.length);
assertEquals(tmpDir + "/dirToDelete", fileMgr.getPathName(fileDeletes[0]));
assertEquals(PathType.TYPE_DIR, fileMgr.getPathType(fileDeletes[0]));
/*
* Unlink a file that doesn't exist.
*/
traceOneProgram(
"#include <fcntl.h>\n" +
"int main() {" +
" int dirfd = open(\"" + tmpDir + "\", O_RDONLY);" +
" unlinkat(dirfd, \"invalidFile\", 0);" +
" return 0;" +
"}", null);
assertEquals(1, fileAccesses.length); /* only the dirfd open */
}
/*-------------------------------------------------------------------------------------*/
}