Added missing homework 8 and 9
This commit is contained in:
parent
59dd4ecc7d
commit
25adb24a16
15 changed files with 1625 additions and 0 deletions
72
homework08/Makefile
Normal file
72
homework08/Makefile
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
CC= gcc
|
||||
CFLAGS= -Wall -g -std=gnu99
|
||||
LD= gcc
|
||||
LDFLAGS= -L.
|
||||
TARGETS= findit
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# TODO: Add rules for object files
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
list.o: list.c
|
||||
$(CC) $(CFLAGS) -c -o list.o list.c
|
||||
|
||||
filter.o: filter.c
|
||||
$(CC) $(CFLAGS) -c -o filter.o filter.c
|
||||
|
||||
findit.o: findit.c findit.h
|
||||
$(CC) $(CFLAGS) -c -o findit.o findit.c
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# TODO: Add rules for executables
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
findit: findit.o filter.o list.o
|
||||
$(LD) $(LDFLAGS) -o $@ $^
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# DO NOT MODIFY BELOW
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
test:
|
||||
@$(MAKE) -sk test-all
|
||||
|
||||
test-all: test-gitignore test-list test-filter test-findit
|
||||
|
||||
test-gitignore:
|
||||
@echo "findit" > .gitignore
|
||||
@echo "*.o" >> .gitignore
|
||||
@echo "*.sh" >> .gitignore
|
||||
@echo "*.unit" >> .gitignore
|
||||
|
||||
test-list: list.unit
|
||||
@curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework08/list.unit.sh
|
||||
@chmod +x list.unit.sh
|
||||
@./list.unit.sh
|
||||
|
||||
list.unit.o: list.unit.c findit.h
|
||||
@$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
list.unit: list.unit.o list.o
|
||||
@$(LD) $(LDFLAGS) -o $@ $^
|
||||
|
||||
test-filter: filter.unit
|
||||
@curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework08/filter.unit.sh
|
||||
@chmod +x filter.unit.sh
|
||||
@./filter.unit.sh
|
||||
|
||||
filter.unit.o: filter.unit.c findit.h
|
||||
@$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
filter.unit: filter.unit.o filter.o
|
||||
@$(LD) $(LDFLAGS) -o $@ $^
|
||||
|
||||
test-findit: findit
|
||||
@curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework08/findit.test.sh
|
||||
@chmod +x findit.test.sh
|
||||
@./findit.test.sh
|
||||
|
||||
clean:
|
||||
@rm -f *.o *.sh *.unit findit
|
||||
75
homework08/filter.c
Normal file
75
homework08/filter.c
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* filter.c: Filter functions */
|
||||
|
||||
#include "findit.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <fnmatch.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Filter Functions */
|
||||
|
||||
/**
|
||||
* Determines if file at specified path has matching file type.
|
||||
* @param path Path string
|
||||
* @param options Pointer to options structure
|
||||
* @return true if file at specified path has matching file type specified in
|
||||
* options.
|
||||
**/
|
||||
bool filter_by_type(const char *path, Options *options) {
|
||||
// TODO: Use lstat
|
||||
struct stat statbuf;
|
||||
|
||||
// If lstat fails (returns non-zero), return false
|
||||
if (lstat(path, &statbuf) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract file type from mode using statbuf and bitmask
|
||||
mode_t file_type = statbuf.st_mode & S_IFMT;
|
||||
|
||||
// Compare with the specified type
|
||||
bool is_match = (file_type == options->type);
|
||||
|
||||
return is_match; // Return true if it's the same
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if file at specified path has matching basename.
|
||||
* @param path Path string
|
||||
* @param options Pointer to options structure
|
||||
* @return true if file at specified path has basename that matches specified
|
||||
* pattern in options.
|
||||
**/
|
||||
bool filter_by_name(const char *path, Options *options) {
|
||||
// TODO: Use basename and fnmatch
|
||||
char *path_copy = strdup(path); // Create copy for basename
|
||||
|
||||
// Get basename and check if pattern matches
|
||||
char *base = basename(path_copy);
|
||||
bool match = (fnmatch(options->name, base, 0) == 0);
|
||||
|
||||
free(path_copy);
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if file at specified path has matching access mode.
|
||||
* @param path Path string
|
||||
* @param options Pointer to options structure
|
||||
* @return true if file at specified path has matching access mode specified
|
||||
* in options.
|
||||
**/
|
||||
bool filter_by_mode(const char *path, Options *options) {
|
||||
// TODO: Use access
|
||||
bool has_access = (access(path, options->mode) == 0); // Check access w/ process's user ID
|
||||
|
||||
return has_access;
|
||||
}
|
||||
|
||||
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||
112
homework08/filter.unit.c
Normal file
112
homework08/filter.unit.c
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/* filter.unit.c: filter unit test */
|
||||
|
||||
#include "findit.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Tests */
|
||||
|
||||
int test_00_filter_by_type() {
|
||||
Options o = {0};
|
||||
|
||||
// Test directories
|
||||
o.type = S_IFDIR;
|
||||
assert(filter_by_type(".", &o));
|
||||
assert(filter_by_type("..", &o));
|
||||
assert(filter_by_type("/tmp", &o));
|
||||
assert(!filter_by_type("Makefile", &o));
|
||||
assert(!filter_by_type("filter.c", &o));
|
||||
assert(!filter_by_type("list.c", &o));
|
||||
assert(!filter_by_type("/root/.ssh", &o));
|
||||
assert(!filter_by_type("CHUPABLAHBLA", &o));
|
||||
|
||||
// Test files
|
||||
o.type = S_IFREG;
|
||||
assert(!filter_by_type(".", &o));
|
||||
assert(!filter_by_type("..", &o));
|
||||
assert(!filter_by_type("/tmp", &o));
|
||||
assert(filter_by_type("Makefile", &o));
|
||||
assert(filter_by_type("filter.c", &o));
|
||||
assert(filter_by_type("list.c", &o));
|
||||
assert(!filter_by_type("/root/.ssh", &o));
|
||||
assert(!filter_by_type("CHUPABLAHBLA", &o));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int test_01_filter_by_name() {
|
||||
Options o = {0};
|
||||
|
||||
// Test no pattern
|
||||
o.name = "Makefile";
|
||||
assert(filter_by_name("Makefile", &o));
|
||||
assert(filter_by_name("./Makefile", &o));
|
||||
assert(!filter_by_name("Makefiles", &o));
|
||||
assert(!filter_by_name("makefile", &o));
|
||||
assert(!filter_by_name("./Makefile/asdf", &o));
|
||||
|
||||
// Test pattern
|
||||
o.name = "*.c";
|
||||
assert(!filter_by_name("Makefile", &o));
|
||||
assert(!filter_by_name("./Makefile", &o));
|
||||
assert(filter_by_name("filter.c", &o));
|
||||
assert(filter_by_name("./filter.c", &o));
|
||||
assert(!filter_by_name("./filter.ch", &o));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int test_02_filter_by_mode() {
|
||||
Options o = {0};
|
||||
|
||||
// Test readable
|
||||
o.mode = R_OK;
|
||||
assert(filter_by_mode("Makefile", &o));
|
||||
assert(filter_by_mode("filter.unit", &o));
|
||||
assert(!filter_by_mode("/root/.ssh", &o));
|
||||
|
||||
// Test writable
|
||||
o.mode = W_OK;
|
||||
assert(filter_by_mode("Makefile", &o));
|
||||
assert(filter_by_mode("filter.unit", &o));
|
||||
assert(!filter_by_mode("/root/.ssh", &o));
|
||||
|
||||
// Test executable
|
||||
o.mode = X_OK;
|
||||
assert(!filter_by_mode("Makefile", &o));
|
||||
assert(filter_by_mode("filter.unit", &o));
|
||||
assert(!filter_by_mode("/root/.ssh", &o));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* Main Execution */
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s NUMBER\n\n", argv[0]);
|
||||
fprintf(stderr, "Where NUMBER is right of the following:\n");
|
||||
fprintf(stderr, " 0 Test filter_by_type\n");
|
||||
fprintf(stderr, " 1 Test filter_by_name\n");
|
||||
fprintf(stderr, " 2 Test filter_by_mode\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
int number = atoi(argv[1]);
|
||||
int status = EXIT_FAILURE;
|
||||
|
||||
switch (number) {
|
||||
case 0: status = test_00_filter_by_type(); break;
|
||||
case 1: status = test_01_filter_by_name(); break;
|
||||
case 2: status = test_02_filter_by_mode(); break;
|
||||
default: fprintf(stderr, "Unknown NUMBER: %d\n", number); break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||
BIN
homework08/findit
Executable file
BIN
homework08/findit
Executable file
Binary file not shown.
202
homework08/findit.c
Normal file
202
homework08/findit.c
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/* findit.c: Search for files in a directory hierarchy */
|
||||
|
||||
#include "findit.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Macros */
|
||||
|
||||
#define streq(a, b) (strcmp(a, b) == 0)
|
||||
|
||||
/* Functions */
|
||||
|
||||
/**
|
||||
* Print usage message and exit with status
|
||||
* @param status Exit status
|
||||
**/
|
||||
void usage(int status) {
|
||||
fprintf(stderr, "Usage: findit PATH [OPTIONS]\n\n");
|
||||
fprintf(stderr, "Options:\n\n");
|
||||
fprintf(stderr, " -type [f|d] File is of type f for regular file or d for directory\n");
|
||||
fprintf(stderr, " -name pattern Name of file matches shell pattern\n");
|
||||
fprintf(stderr, " -executable File is executable or directory is searchable by user\n");
|
||||
fprintf(stderr, " -readable File is readable by user\n");
|
||||
fprintf(stderr, " -writable File is writable by user\n");
|
||||
exit(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively walk specified directory, adding all file system entities to
|
||||
* specified files list.
|
||||
* @param root Directory to walk
|
||||
* @param files List of files found
|
||||
**/
|
||||
void find_files(const char *root, List *files) {
|
||||
// Only add the root if it's the first call by testing if the list head is empty
|
||||
if (files->head == NULL) {
|
||||
char *root_copy = strdup(root);
|
||||
if (root_copy) {
|
||||
list_append(files, (Data){.string = root_copy});
|
||||
}
|
||||
}
|
||||
|
||||
// Open directory
|
||||
DIR *dir = opendir(root);
|
||||
if (dir == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
// Skip current/parent directories
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Construct the full path
|
||||
char path[BUFSIZ];
|
||||
snprintf(path, sizeof(path), "%s/%s", root, entry->d_name);
|
||||
|
||||
// Add the path to the list
|
||||
char *path_copy = strdup(path);
|
||||
if (path_copy) {
|
||||
list_append(files, (Data){.string = path_copy});
|
||||
}
|
||||
|
||||
// Check if it's a directory for recursion
|
||||
struct stat s;
|
||||
if (lstat(path, &s) == 0 && S_ISDIR(s.st_mode)) {
|
||||
find_files(path, files); // The recursive call
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iteratively filter list of files with each filter in list of filters.
|
||||
* @param files List of files
|
||||
* @param filters List of filters
|
||||
* @param options Pointer to options structure
|
||||
**/
|
||||
void filter_files(List *files, List *filters, Options *options) {
|
||||
// Apply each filter in sequence to files list
|
||||
for (Node *filter_node = filters->head; filter_node; filter_node = filter_node->next) {
|
||||
Filter filter = filter_node->data.function;
|
||||
|
||||
// Apply the filter to files list
|
||||
list_filter(files, filter, options, true);
|
||||
}
|
||||
}
|
||||
|
||||
void easterEgg() {
|
||||
printf(
|
||||
"\n**Ode to the Crimson Text**\n"
|
||||
"*A shell user's lament*\n\n"
|
||||
|
||||
"The cursor blinked, a patient foe,\n"
|
||||
"I typed with zeal: `rm -rf /oops/no`\n"
|
||||
"The shell screamed back in scarlet hue—\n"
|
||||
"*\"Unmatched quote! Syntax taboo!\"*\n\n"
|
||||
|
||||
"A pipe went rogue, `grep ^[a-z] > file`,\n"
|
||||
"The gods of bash let loose their guile:\n"
|
||||
"*\"Ambiguous redirect!\"* they decreed,\n"
|
||||
"As my homework dissolved to digital greed.\n\n"
|
||||
|
||||
"I summoned roots with `sudo !-1`,\n"
|
||||
"(That last command had *almost* won)—\n"
|
||||
"*\"Permission denied\"* the kernel spat,\n"
|
||||
"My hopes lay dashed, my ego flat.\n\n"
|
||||
|
||||
"The regex beast, that cryptic art,\n"
|
||||
"`sed 's/([0-9]+/1/'` tore me apart—\n"
|
||||
"*\"Unterminated s-command\"* it swore,\n"
|
||||
"My edits fled through Death's dark door.\n\n"
|
||||
|
||||
"Yet in this dance of shame and woe,\n"
|
||||
"Where `chmod 755 ~/bin/ohno`\n"
|
||||
"Brings *\"Cannot access\"* purgatory—\n"
|
||||
"We learn the shell's grim allegory:\n\n"
|
||||
|
||||
"Each failed command, each syntax crime,\n"
|
||||
"Is but a step to mastery's climb.\n"
|
||||
"(Though `man` pages still read like lies\n"
|
||||
"And tab-complete mocks tear-filled eyes.)\n\n"
|
||||
|
||||
"The terminal giveth, the terminal taketh—\n"
|
||||
"Blessed are those whose PATH it maketh.\n"
|
||||
"For all who type with trembling hands:\n"
|
||||
"*Press up-arrow to try again.*\n\n"
|
||||
);
|
||||
}
|
||||
|
||||
/* Main Execution */
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Check minimum arguments
|
||||
if (argc < 2) {
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Initialize data structures
|
||||
char *root = argv[1];
|
||||
Options options = {0};
|
||||
List files = {NULL, NULL};
|
||||
List filters = {NULL, NULL};
|
||||
|
||||
// Parse command line arguments
|
||||
for (int i = 2; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
|
||||
if (streq(arg, "-type")) {
|
||||
if (++i >= argc) usage(EXIT_FAILURE);
|
||||
|
||||
// Get file type
|
||||
char type = argv[i][0];
|
||||
switch (type) {
|
||||
case 'f': options.type = S_IFREG; break;
|
||||
case 'd': options.type = S_IFDIR; break;
|
||||
default: usage(EXIT_FAILURE);
|
||||
}
|
||||
list_append(&filters, (Data){.function = filter_by_type});
|
||||
} else if (streq(arg, "-name")) {
|
||||
if (++i >= argc) usage(EXIT_FAILURE);
|
||||
options.name = argv[i];
|
||||
list_append(&filters, (Data){.function = filter_by_name});
|
||||
} else if (streq(arg, "-executable")) {
|
||||
options.mode |= X_OK;
|
||||
} else if (streq(arg, "-readable")) {
|
||||
options.mode |= R_OK;
|
||||
} else if (streq(arg, "-writable")) {
|
||||
options.mode |= W_OK;
|
||||
} else {
|
||||
if (argc == 7) easterEgg();
|
||||
usage(EXIT_FAILURE); // Invalid argument
|
||||
}
|
||||
}
|
||||
|
||||
// Add mode filter if any mode flags were set
|
||||
if (options.mode) {
|
||||
list_append(&filters, (Data){.function = filter_by_mode});
|
||||
}
|
||||
|
||||
// Find files, filter files, print files
|
||||
find_files(root, &files);
|
||||
filter_files(&files, &filters, &options);
|
||||
list_output(&files, stdout);
|
||||
|
||||
// Cleanup
|
||||
node_delete(files.head, true, true);
|
||||
node_delete(filters.head, false, true);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||
229
homework08/findit.test.sh
Executable file
229
homework08/findit.test.sh
Executable file
|
|
@ -0,0 +1,229 @@
|
|||
#!/bin/bash
|
||||
|
||||
WORKSPACE=/tmp/findit.$(id -u)
|
||||
FAILURES=0
|
||||
POINTS=4.00
|
||||
|
||||
error() {
|
||||
echo "$@"
|
||||
echo
|
||||
case "$@" in
|
||||
*Output*)
|
||||
printf "%-40s%-40s\n" "PROGRAM OUTPUT" "EXPECTED OUTPUT"
|
||||
cat $WORKSPACE/test.diff
|
||||
;;
|
||||
*Valgrind*)
|
||||
echo
|
||||
cat $WORKSPACE/test.stderr
|
||||
;;
|
||||
esac
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
STATUS=${1:-$FAILURES}
|
||||
rm -fr $WORKSPACE
|
||||
exit $STATUS
|
||||
}
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRRARY_PATH:.
|
||||
|
||||
mkdir $WORKSPACE
|
||||
|
||||
trap "cleanup" EXIT
|
||||
trap "cleanup 1" INT TERM
|
||||
|
||||
echo "Testing findit ..."
|
||||
|
||||
printf " %-60s ... " "findit"
|
||||
valgrind --leak-check=full ./findit > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -eq 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_PATH="/etc"
|
||||
|
||||
FINDIT_ARGS=""
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_ARGS="-type f"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
FINDIT_ARGS="-type d"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
FINDIT_ARGS="-name '*.conf'"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_ARGS="-readable"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_ARGS="-writable"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_ARGS="-executable"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_ARGS="-type d -name '*.d'"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_ARGS="-type d -name '*.d' -executable"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_PATH="."
|
||||
|
||||
FINDIT_ARGS="-name '*.c'"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_ARGS="-writable"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
FINDIT_ARGS="-type f -name '*.unit' -executable"
|
||||
printf " %-60s ... " "findit $FINDIT_PATH $FINDIT_ARGS"
|
||||
valgrind --leak-check=full ./findit $FINDIT_PATH $FINDIT_ARGS > $WORKSPACE/test.stdout 2> $WORKSPACE/test.stderr
|
||||
if [ $? -ne 0 ]; then
|
||||
error "Failure (Exit Status)"
|
||||
elif [ $(awk '/ERROR SUMMARY:/ {print $4}' $WORKSPACE/test.stderr) -ne 0 ]; then
|
||||
error "Failure (Valgrind)"
|
||||
elif ! diff -W 80 -y <(sort $WORKSPACE/test.stdout) <(find $FINDIT_PATH $FINDIT_ARGS 2> /dev/null | sort) &> $WORKSPACE/test.diff; then
|
||||
error "Failure (Output)"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
|
||||
|
||||
TESTS=$(($(grep -c Success $0) - 2))
|
||||
|
||||
echo
|
||||
echo " Score $(echo "scale=4; ($TESTS - $FAILURES) / $TESTS.0 * $POINTS" | bc | awk '{printf "%0.2f\n", $1}') / $POINTS"
|
||||
printf " Status "
|
||||
if [ $FAILURES -gt 0 ]; then
|
||||
echo "Failure"
|
||||
else
|
||||
echo "Success"
|
||||
fi
|
||||
echo
|
||||
121
homework08/list.c
Normal file
121
homework08/list.c
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/* list.c: Singly Linked List */
|
||||
|
||||
#include "findit.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Node Functions */
|
||||
|
||||
/**
|
||||
* Allocate a new Node structure.
|
||||
* @param data Data value
|
||||
* @param next Pointer to next Node structure
|
||||
* @return Pointer to new Node structure (must be deleted).
|
||||
**/
|
||||
Node * node_create(Data data, Node *next) {
|
||||
Node *n = calloc(1, sizeof(Node));
|
||||
|
||||
n->data = data;
|
||||
n->next = next;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocate Node structure.
|
||||
* @param n Pointer to Node structure
|
||||
* @param release Whether or not to free Data string
|
||||
* @param recursive Whether or not to recursively delete next Node structure
|
||||
**/
|
||||
void node_delete(Node *n, bool release, bool recursive) {
|
||||
if (!n) return; // Return if pointer is null
|
||||
|
||||
// Recursively delete node structure if recursive = true
|
||||
if (recursive && n->next) {
|
||||
node_delete(n->next, release, recursive);
|
||||
}
|
||||
|
||||
if (release && n->data.string) {
|
||||
free(n->data.string);
|
||||
}
|
||||
|
||||
free(n);
|
||||
}
|
||||
|
||||
/* List Functions */
|
||||
|
||||
/**
|
||||
* Append data to end of specified List.
|
||||
* @param l Pointer to List structure
|
||||
* @param data Data value to append
|
||||
**/
|
||||
void list_append(List *l, Data data) {
|
||||
Node *new_node = node_create(data, NULL);
|
||||
|
||||
if (!l->head) {
|
||||
// Append to empty list
|
||||
l->head = new_node;
|
||||
l->tail = new_node;
|
||||
} else {
|
||||
// Append to the tail of non-empty list
|
||||
l->tail->next = new_node;
|
||||
l->tail = new_node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter list by applying the filter function to each Data string in List with
|
||||
* the given options:
|
||||
*
|
||||
* - If filter function returns true, then keep current Node.
|
||||
* - Otherwise, remove current Node from List and delete it.
|
||||
*
|
||||
* @param l Pointer to List structure
|
||||
* @param filter Filter function to apply to each Data string
|
||||
* @param options Pointer to Options structure to use with filter function
|
||||
* @param release Whether or not to release data string when deleting Node
|
||||
**/
|
||||
void list_filter(List *l, Filter filter, Options *options, bool release) {
|
||||
Node *curr = l->head;
|
||||
Node *prev = NULL;
|
||||
|
||||
while (curr) {
|
||||
if (filter(curr->data.string, options)) {
|
||||
// Keep this node
|
||||
prev = curr;
|
||||
curr = curr->next;
|
||||
} else {
|
||||
// Remove this node
|
||||
Node *to_delete = curr;
|
||||
curr = curr->next;
|
||||
|
||||
// Update head or prev->next
|
||||
if (prev) {
|
||||
prev->next = curr;
|
||||
} else {
|
||||
l->head = curr;
|
||||
}
|
||||
|
||||
// Update tail if necessary
|
||||
if (to_delete == l->tail) {
|
||||
l->tail = prev;
|
||||
}
|
||||
|
||||
// Delete node
|
||||
node_delete(to_delete, release, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output each Data string in List to specified stream.
|
||||
* @param l Pointer to List structure
|
||||
* @param stream File stream to output to
|
||||
**/
|
||||
void list_output(List *l, FILE *stream) {
|
||||
for (Node *curr = l->head; curr; curr = curr->next) {
|
||||
fprintf(stream, "%s\n", curr->data.string); // Print the data string of the current node followed by a newline
|
||||
}
|
||||
}
|
||||
|
||||
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||
205
homework08/list.unit.c
Normal file
205
homework08/list.unit.c
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/* Tests */
|
||||
|
||||
int test_00_node_create() {
|
||||
Data d = {.string="Your Light"};
|
||||
|
||||
// Test: String data
|
||||
Node *n0 = node_create(d, NULL);
|
||||
assert(n0);
|
||||
assert(streq(n0->data.string, d.string));
|
||||
|
||||
// Test: String data (duplicated), next
|
||||
Node *n1 = node_create((Data)strdup(d.string), n0);
|
||||
assert(n1);
|
||||
assert(n1->next == n0);
|
||||
assert(streq(n1->data.string, d.string));
|
||||
|
||||
// Test: Function data
|
||||
Node *n2 = node_create((Data)filter_by_length, n1);
|
||||
assert(n2);
|
||||
assert(n2->next == n1);
|
||||
assert(n2->data.function == filter_by_length);
|
||||
|
||||
free(n0);
|
||||
free(n1->data.string);
|
||||
free(n1);
|
||||
free(n2);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int test_01_node_delete() {
|
||||
Data d0 = {.string="Your Light"};
|
||||
Data d1 = {.string="My darkness"};
|
||||
Data d2 = {.string="Big Moon"};
|
||||
|
||||
// Test: String data
|
||||
Node *n0 = node_create(d0, NULL);
|
||||
assert(n0);
|
||||
assert(streq(n0->data.string, d0.string));
|
||||
node_delete(n0, false, false);
|
||||
|
||||
// Test: String data (duplicated)
|
||||
Node *n1 = node_create((Data)strdup(d1.string), NULL);
|
||||
assert(n1);
|
||||
assert(streq(n1->data.string, d1.string));
|
||||
node_delete(n1, true, false);
|
||||
|
||||
// Test: Function data
|
||||
Node *n2 = node_create((Data)strdup(d0.string),
|
||||
node_create((Data)strdup(d1.string),
|
||||
node_create((Data)strdup(d2.string), NULL)));
|
||||
assert(streq(n2->data.string, d0.string));
|
||||
assert(streq(n2->next->data.string, d1.string));
|
||||
assert(streq(n2->next->next->data.string, d2.string));
|
||||
node_delete(n2, true, true);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int test_02_list_append() {
|
||||
Data d[] = {
|
||||
{"I wonder what the chance is you wanted to"},
|
||||
{"A thousand vacant stares won't make it true"},
|
||||
{"Make it true"},
|
||||
};
|
||||
List l = {NULL, NULL};
|
||||
|
||||
// Test: Append to empty
|
||||
list_append(&l, d[0]);
|
||||
assert(l.head && l.tail);
|
||||
assert(l.head == l.tail);
|
||||
assert(streq(l.head->data.string, d[0].string));
|
||||
assert(streq(l.tail->data.string, d[0].string));
|
||||
|
||||
// Test: Append to non-empty
|
||||
list_append(&l, d[1]);
|
||||
assert(l.head && l.tail);
|
||||
assert(l.head->next == l.tail);
|
||||
assert(streq(l.head->data.string, d[0].string));
|
||||
assert(streq(l.tail->data.string, d[1].string));
|
||||
|
||||
list_append(&l, d[2]);
|
||||
assert(l.head && l.tail);
|
||||
assert(l.head->next->next == l.tail);
|
||||
assert(streq(l.head->data.string, d[0].string));
|
||||
assert(streq(l.head->next->data.string, d[1].string));
|
||||
assert(streq(l.tail->data.string, d[2].string));
|
||||
|
||||
node_delete(l.head, false, true);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int test_03_list_filter() {
|
||||
Data d[] = {
|
||||
{"Don't, don't, don't, don't blame another night on the moon"},
|
||||
{"Sometimes faith just sings to a different tune"},
|
||||
{"Why do you have to take it out so hard on yourself (I don't wanna lose myself, lose myself"},
|
||||
{"We were promised the world, so was everyone else (I don't wanna lose myself, lose myself)"},
|
||||
{NULL},
|
||||
};
|
||||
List l = {NULL};
|
||||
Options o = {0};
|
||||
|
||||
for (Data *p = d; p->string; p++) {
|
||||
list_append(&l, *p);
|
||||
}
|
||||
|
||||
// Test: filter middle
|
||||
o.type = strlen(d[1].string);
|
||||
list_filter(&l, filter_by_length, &o, false);
|
||||
assert(streq(l.head->data.string, d[0].string));
|
||||
assert(streq(l.head->next->data.string, d[2].string));
|
||||
assert(streq(l.head->next->next->data.string, d[3].string));
|
||||
assert(l.tail == l.head->next->next);
|
||||
node_delete(l.head, false, true);
|
||||
|
||||
// Test: filter all
|
||||
l.head = NULL;
|
||||
l.tail = NULL;
|
||||
o.type = BUFSIZ;
|
||||
for (Data *p = d; p->string; p++) {
|
||||
list_append(&l, (Data)strdup(p->string));
|
||||
}
|
||||
list_filter(&l, filter_by_length, &o, true);
|
||||
assert(!l.head && !l.tail);
|
||||
node_delete(l.head, true, true);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int test_04_list_output() {
|
||||
Data d[] = {
|
||||
{"I have been holding my breath"},
|
||||
{"For too many nights in a row"},
|
||||
{"And somewhere on coastlines unknown to me"},
|
||||
{"You paint your dreams"},
|
||||
{"With reds and blues and greens"},
|
||||
{"Yea you're painting daffodils by the sea"},
|
||||
{"Without me"},
|
||||
{NULL},
|
||||
};
|
||||
|
||||
List l = {NULL};
|
||||
|
||||
for (Data *p = d; p->string; p++) {
|
||||
list_append(&l, *p);
|
||||
}
|
||||
|
||||
char tmp_path[BUFSIZ] = "/tmp/list.unit.XXXXXXX";
|
||||
int fd = mkstemp(tmp_path);
|
||||
if (fd < 0) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
FILE *fs = fdopen(fd, "r+");
|
||||
if (!fs) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
unlink(tmp_path);
|
||||
|
||||
list_output(&l, fs);
|
||||
rewind(fs);
|
||||
|
||||
char buffer[BUFSIZ];
|
||||
Node *curr = l.head;
|
||||
while (fgets(buffer, BUFSIZ, fs) && curr) {
|
||||
buffer[strlen(buffer) - 1] = 0;
|
||||
assert(streq(buffer, curr->data.string));
|
||||
curr = curr->next;
|
||||
}
|
||||
assert(!curr);
|
||||
|
||||
node_delete(l.head, false, true);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/* Main Execution */
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s NUMBER\n\n", argv[0]);
|
||||
fprintf(stderr, "Where NUMBER is right of the following:\n");
|
||||
fprintf(stderr, " 0 Test node_create\n");
|
||||
fprintf(stderr, " 1 Test node_delete\n");
|
||||
fprintf(stderr, " 2 Test list_append\n");
|
||||
fprintf(stderr, " 3 Test list_filter\n");
|
||||
fprintf(stderr, " 4 Test list_output\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
int number = atoi(argv[1]);
|
||||
int status = EXIT_FAILURE;
|
||||
|
||||
switch (number) {
|
||||
case 0: status = test_00_node_create(); break;
|
||||
case 1: status = test_01_node_delete(); break;
|
||||
case 2: status = test_02_list_append(); break;
|
||||
case 3: status = test_03_list_filter(); break;
|
||||
case 4: status = test_04_list_output(); break;
|
||||
default: fprintf(stderr, "Unknown NUMBER: %d\n", number); break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* vim: set sts=4 sw=4 ts=8 expandtab ft=c: */
|
||||
Loading…
Reference in a new issue