diff --git a/collect_coverage.sh b/collect_coverage.sh
new file mode 100755
index 00000000..9912841e
--- /dev/null
+++ b/collect_coverage.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+set -ex
+
+CONTAINER_NAME=$1
+VOLUME_NAME=$2
+DEST_DIR=$3
+BUILD_PATH=$4
+SRC_NAME=$5
+EXC_NAME=$6
+# Ensure the local coverage directory exists
+
+# Execute commands inside the container to process gcov files
+mkdir -p $DEST_DIR
+CUR_DIR=$(pwd)
+cd $DEST_DIR
+rm -rf ./*
+cd $CUR_DIR
+podman exec -u 0 -it $CONTAINER_NAME /bin/bash -c '
+ cd /coverage
+ llvm-profdata merge -sparse *.profraw -o coverage.profdata
+ mkdir -p cov
+ mkdir -p '$BUILD_PATH'
+ cp ./'$SRC_NAME' '$BUILD_PATH'/'$SRC_NAME'
+ llvm-cov show /usr/local/bin/'$EXC_NAME' -instr-profile=coverage.profdata -format=html -output-dir=./cov
+'
+cp -r /var/lib/containers/storage/volumes/$VOLUME_NAME/_data/cov/* $DEST_DIR
+chown 100:100 -R $DEST_DIR
+chmod 777 -R $DEST_DIR
\ No newline at end of file
diff --git a/collect_pop_smtp_coverage.sh b/collect_pop_smtp_coverage.sh
new file mode 100755
index 00000000..83aa58af
--- /dev/null
+++ b/collect_pop_smtp_coverage.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -ex
+
+./collect_coverage.sh singularity_smtp_1 singularity_smtp_coverage ./smtp_coverage/ /smtp smtp.c smtp_
+./collect_coverage.sh singularity_pop_1 singularity_pop_coverage ./pop_coverage/ /pop pop3.c pop3_
diff --git a/container-compose.yml b/container-compose.yml
index eb67057b..4ed0295b 100644
--- a/container-compose.yml
+++ b/container-compose.yml
@@ -78,6 +78,7 @@ services:
target: smtp
args:
hostname: ${SINGULARITY_HOSTNAME}
+ coverage: 1
LISTEN_PORT: 1465
volumes:
- type: volume
@@ -92,6 +93,12 @@ services:
source: email-patchsets
target: /var/lib/email/patchsets
read_only: false
+ - type: volume
+ source: smtp_coverage
+ target: /coverage
+ read_only: false
+ environment:
+ COVERAGE: ${COVERAGE-0}
networks:
- smtp
pop:
@@ -113,9 +120,15 @@ services:
source: email-journal
target: /var/lib/email/journal
read_only: true
+ - type: volume
+ source: pop_coverage
+ target: /coverage
+ read_only: false
depends_on:
- smtp
- denis
+ environment:
+ COVERAGE: ${COVERAGE-0}
networks:
- pop
submatrix:
@@ -246,3 +259,5 @@ volumes:
submatrix-data:
denis-db:
git-repos:
+ smtp_coverage:
+ pop_coverage:
\ No newline at end of file
diff --git a/denis/Containerfile b/denis/Containerfile
index cc12ade3..083e8c93 100644
--- a/denis/Containerfile
+++ b/denis/Containerfile
@@ -4,6 +4,16 @@ RUN apk add \
make \
;
+RUN if [ "$COVERAGE" -eq 1 ]; then \
+ apk add \
+ clang-dev \
+ compiler-rt \
+ llvm-dev \
+ gettext \
+ bash \
+ ; \
+ fi
+
COPY --from=run_at_source . /run-at
RUN make -C /run-at CC='clang -static'
diff --git a/orbit/radius.py b/orbit/radius.py
index 2c480949..b5784112 100644
--- a/orbit/radius.py
+++ b/orbit/radius.py
@@ -293,7 +293,12 @@ def mk_form_welcome(session):
'''
@@ -452,7 +457,12 @@ def gradeable_row(self, item_name, gradeable, rightmost_col):
{item_name}
- {datetime.fromtimestamp(gradeable.timestamp).astimezone().isoformat() if gradeable else '-'}
+ {(
+ datetime.fromtimestamp(gradeable.timestamp)
+ .astimezone()
+ .isoformat()
+ if gradeable else '-'
+ )}
|
{gradeable.submission_id if gradeable else '-'}
@@ -475,7 +485,13 @@ def oopsie_button(self):
def body(self):
if self.oopsieness == OopsStatus.USED_HERE:
return f"""
- {self.gradeable_row('Final Submission', self.final, self.oopsie_button())}
+ {(
+ self.gradeable_row(
+ 'Final Submission',
+ self.final,
+ self.oopsie_button()
+ )
+ )}
|
| Comments |
- |
@@ -485,14 +501,26 @@ def body(self):
(int(datetime.now().timestamp())
< self.assignment.initial_due_date)):
return f"""
- {self.gradeable_row('Initial Submission', self.init, self.oopsie_button())}
+ {(
+ self.gradeable_row(
+ 'Initial Submission',
+ self.init,
+ self.oopsie_button()
+ )
+ )}
| Automated Feedback |
- |
"""
return f"""
- {self.gradeable_row('Initial Submission', self.init, self.oopsie_button())}
+ {(
+ self.gradeable_row(
+ 'Initial Submission',
+ self.init,
+ self.oopsie_button()
+ )
+ )}
| Automated Feedback |
- |
@@ -503,9 +531,19 @@ def body(self):
Submission ID |
Score |
- {self.gradeable_row(self.peer1 + ' Peer Review', self.review1, '-') if self.peer1 else ''}
- {self.gradeable_row(self.peer2 + ' Peer Review', self.review2, '-') if self.peer2 else ''}
- {self.gradeable_row('Final Submission', self.final, '-')}
+ {(
+ self.gradeable_row(
+ self.peer1 + ' Peer Review',
+ self.review1, '-') if self.peer1 else ''
+ )}
+ {(
+ self.gradeable_row(
+ self.peer2 + ' Peer Review',
+ self.review2, '-') if self.peer2 else ''
+ )}
+ {(
+ self.gradeable_row('Final Submission', self.final, '-')
+ )}
| Comments |
- |
@@ -631,7 +669,10 @@ def form_respond():
username, password = creds
rocket.msg('welcome to the classroom')
return rocket.respond(f'''
- Save these credentials, you will not be able to access them again
+
+ Save these credentials, you will not be able to access them again
+
+
Username: {username}
Password: {password}
''')
diff --git a/pop/Containerfile b/pop/Containerfile
index 9e65d4fa..1712ba45 100644
--- a/pop/Containerfile
+++ b/pop/Containerfile
@@ -1,9 +1,19 @@
-FROM alpine:3.20 AS build
+FROM alpine:3.20 AS pop
RUN apk add \
clang \
make \
;
+RUN if [ "$COVERAGE" -eq 1 ]; then \
+ apk add \
+ clang-dev \
+ compiler-rt \
+ llvm-dev \
+ gettext \
+ bash \
+ ; \
+ fi
+
COPY --from=tcp_server_source . /tcp_server
ARG LISTEN_PORT=995
RUN make -C /tcp_server CC='clang -static' DEFAULT_PORT=${LISTEN_PORT}
@@ -14,10 +24,26 @@ COPY . /pop
RUN make -C /pop CC='clang -static'
-FROM scratch as pop
-
-COPY --from=build /tcp_server/tcp_server /usr/local/bin/tcp_server
-COPY --from=build /pop/pop3 /usr/local/bin/pop3
+RUN if [ "$COVERAGE" -eq 1 ]; then \
+ make -C /pop CC='clang -static' SRVNAME=$hostname COVERAGE=1; \
+ else \
+ make -C /pop CC='clang -static' SRVNAME=$hostname; \
+ fi
+
+# FROM scratch as pop
+
+RUN cp /tcp_server/tcp_server /usr/local/bin/tcp_server
+RUN if [ "$COVERAGE" -eq 1 ]; then \
+ cp /pop/pop3 /usr/local/bin/pop3_; \
+ cp /pop/pop_wrapper /usr/local/bin/pop3; \
+ mkdir -p /coverage; \
+ chown 100:100 /coverage; \
+ cp /pop/pop3.c /coverage/pop3.c; \
+ else \
+ cp /pop/pop3 /usr/local/bin/pop3; \
+ fi
+
+RUN rm -rf /tcp_server /pop /journal /pop
USER 100:100
diff --git a/pop/Makefile b/pop/Makefile
index 95fd379e..f4b77276 100644
--- a/pop/Makefile
+++ b/pop/Makefile
@@ -2,16 +2,25 @@ CC = clang
CFLAGS = -std=c2x -Weverything -Wno-unsafe-buffer-usage -Wno-c++98-compat -Wno-gnu-designator -Wno-gnu-case-range -Wno-initializer-overrides \
-Wno-declaration-after-statement -Wno-four-char-constants -Wno-pre-c2x-compat -Wno-disabled-macro-expansion -Wno-switch -Wno-switch-enum -D_GNU_SOURCE
+# Add flags for coverage support
+ADDITIONAL_CFLAGS =
+ifeq ($(COVERAGE), 1)
+ ADDITIONAL_CFLAGS = -fprofile-instr-generate -fcoverage-mapping -mllvm -runtime-counter-relocation
+endif
+
ifdef DEBUG
CFLAGS += -DDEBUG -Og -g
endif
.PHONY: all clean
-all: pop3
+all: pop3 pop_wrapper
pop3: pop3.c journal/email.h
- $(CC) $(CFLAGS) -o $@ $<
+ $(CC) $(CFLAGS) $(ADDITIONAL_CFLAGS) -o $@ $<
+
+pop_wrapper: pop_wrapper.c
+ $(CC) $(CFLAGS) -o $@ $^
clean:
- -rm pop3
+ -rm pop3
\ No newline at end of file
diff --git a/pop/pop_wrapper.c b/pop/pop_wrapper.c
new file mode 100644
index 00000000..58f6546f
--- /dev/null
+++ b/pop/pop_wrapper.c
@@ -0,0 +1,34 @@
+#include
+#include
+
+int main(int argc, char **argv) {
+ // Set the environment variable
+ int result = setenv("LLVM_PROFILE_FILE", "/coverage/coverage-%p.profraw%c", 1);
+
+ if (result != 0) {
+ return 1;
+ }
+
+ // Define the program to execute (predefined)
+ char *program ="/usr/local/bin/pop3_";
+
+ // Create a new array to hold the arguments for execvp, including the program name
+ char** exec_args = (char**) malloc(sizeof(char*) * (unsigned long) (argc + 1));
+
+ // Set the program ßßßname as the first argument
+ exec_args[0] = program;
+
+ // Copy the rest of the arguments passed to your program to the new exec_args array
+ for (int i = 1; i < argc; i++) {
+ exec_args[i] = argv[i];
+ }
+
+ // Terminate the array with a NULL pointer (as required by execvp)
+ exec_args[argc] = NULL;
+
+ // Execute the program, replacing the current process
+ execvp(program, exec_args);
+
+ // If execvp returns, it means it failed
+ return 1;
+}
diff --git a/smtp/Containerfile b/smtp/Containerfile
index ac17fb3c..5adac32d 100644
--- a/smtp/Containerfile
+++ b/smtp/Containerfile
@@ -1,9 +1,19 @@
-FROM alpine:3.20 AS build
+FROM alpine:3.20 AS smtp
RUN apk add \
clang \
+ git \
make \
;
+RUN if [ "$COVERAGE" -eq 1 ]; then \
+ apk add \
+ clang-dev \
+ compiler-rt \
+ llvm-dev \
+ bash \
+ ; \
+fi
+
COPY --from=tcp_server_source . /tcp_server
ARG LISTEN_PORT=465
RUN make -C /tcp_server CC='clang -static' DEFAULT_PORT=${LISTEN_PORT}
@@ -14,18 +24,31 @@ COPY . /smtp
ARG hostname
RUN test -n "$hostname" || (echo 'hostname is not set' && false)
-RUN make -C /smtp CC='clang -static' SRVNAME=$hostname
+# RUN make -C /smtp CC='clang -static' SRVNAME=$hostname
+
+RUN if [ "$COVERAGE" -eq 1 ]; then \
+ make -C /smtp CC='clang -static' SRVNAME=$hostname COVERAGE=1; \
+ else \
+ make -C /smtp CC='clang -static' SRVNAME=$hostname; \
+ fi
RUN mkdir -p /var/lib/email/mail /var/lib/email/logs && \
chown 100:100 /var/lib/email/mail /var/lib/email/logs && \
:
-FROM scratch as smtp
+RUN cp /tcp_server/tcp_server /usr/local/bin/tcp_server
-COPY --from=build /var/lib/email /var/lib/email
-COPY --from=build /tcp_server/tcp_server /usr/local/bin/tcp_server
-COPY --from=build /smtp/smtp /usr/local/bin/smtp
+RUN if [ "$COVERAGE" -eq 1 ]; then \
+ cp /smtp/smtp_wrapper /usr/local/bin/smtp; \
+ cp /smtp/smtp /usr/local/bin/smtp_; \
+ mkdir -p /coverage; \
+ chown 100:100 /coverage; \
+ cp /smtp/smtp.c /coverage/smtp.c; \
+ else \
+ cp /smtp/smtp /usr/local/bin/smtp; \
+ fi
+RUN rm -rf /smtp /tcp_server
USER 100:100
ARG LISTEN_PORT=465
diff --git a/smtp/Makefile b/smtp/Makefile
index 8ea9e43e..8e07a882 100644
--- a/smtp/Makefile
+++ b/smtp/Makefile
@@ -3,20 +3,28 @@ CFLAGS = -std=c2x -Weverything -Wno-unsafe-buffer-usage -Wno-c++98-compat -Wno-g
-Wno-initializer-overrides -Wno-declaration-after-statement -Wno-four-char-constants \
-Wno-pre-c2x-compat -Wno-disabled-macro-expansion -D_GNU_SOURCE -DHOSTNAME='"$(SRVNAME)"'
+# Add flags for coverage support
+ADDITIONAL_CFLAGS =
+ifeq ($(COVERAGE), 1)
+ ADDITIONAL_CFLAGS = -fprofile-instr-generate -fcoverage-mapping -mllvm -runtime-counter-relocation
+endif
+
ifdef DEBUG
CFLAGS += -DDEBUG -Og -g
endif
.PHONY: all clean
-all: smtp
+all: smtp smtp_wrapper
smtp: smtp.c
ifndef SRVNAME
$(error "you must pass SRVNAME=")
endif
+ $(CC) $(CFLAGS) $(ADDITIONAL_CFLAGS) -o $@ $^
+
+smtp_wrapper: smtp_wrapper.c
$(CC) $(CFLAGS) -o $@ $^
clean:
-rm smtp
-
diff --git a/smtp/smtp_wrapper.c b/smtp/smtp_wrapper.c
new file mode 100644
index 00000000..701235ab
--- /dev/null
+++ b/smtp/smtp_wrapper.c
@@ -0,0 +1,34 @@
+#include
+#include
+
+int main(int argc, char **argv) {
+ // Set the environment variable
+ int result = setenv("LLVM_PROFILE_FILE", "/coverage/coverage-%p.profraw%c", 1);
+
+ if (result != 0) {
+ return 1;
+ }
+
+ // Define the program to execute (predefined)
+ char *program ="/usr/local/bin/smtp_";
+
+ // Create a new array to hold the arguments for execvp, including the program name
+ char** exec_args = (char**) malloc(sizeof(char*) * (unsigned long) (argc + 1));
+
+ // Set the program ßßßname as the first argument
+ exec_args[0] = program;
+
+ // Copy the rest of the arguments passed to your program to the new exec_args array
+ for (int i = 1; i < argc; i++) {
+ exec_args[i] = argv[i];
+ }
+
+ // Terminate the array with a NULL pointer (as required by execvp)
+ exec_args[argc] = NULL;
+
+ // Execute the program, replacing the current process
+ execvp(program, exec_args);
+
+ // If execvp returns, it means it failed
+ return 1;
+}
diff --git a/test.sh b/test.sh
index b34d2dea..b262744a 100755
--- a/test.sh
+++ b/test.sh
@@ -119,6 +119,7 @@ curl --url "pop3s://$SINGULARITY_HOSTNAME" \
| diff <(printf '\r\n') /dev/stdin
CR=$(printf "\r")
+
# Check that the user can send a message to the server
(
curl --url "smtps://$SINGULARITY_HOSTNAME" \
@@ -138,6 +139,71 @@ EOF
| diff <(printf "") /dev/stdin
+# Check that the user can send an email to multiple recipients
+(
+curl --url "smtps://$SINGULARITY_HOSTNAME" \
+ --unix-socket ./socks/smtps.sock \
+ "${CURL_OPTS[@]}" \
+ --mail-from "user@$SINGULARITY_HOSTNAME" \
+ --mail-rcpt "other@$SINGULARITY_HOSTNAME" \
+ --mail-rcpt "other1@$SINGULARITY_HOSTNAME" \
+ --upload-file - \
+ --user "user:${REGISTER_PASS}" <$CR
+To: "other1@$SINGULARITY_HOSTNAME" $CR
+$CR
+To whom it may concern,$CR
+$CR
+Bottom text$CR
+EOF
+) | tee test/smtp_send_email_multi_recipients \
+ | diff <(printf "") /dev/stdin
+
+
+# Check that the user can send an email with Cc (carbon copy) recipients
+(
+curl --url "smtps://$SINGULARITY_HOSTNAME" \
+ --unix-socket ./socks/smtps.sock \
+ "${CURL_OPTS[@]}" \
+ --mail-from "user@$SINGULARITY_HOSTNAME" \
+ --mail-rcpt "other@$SINGULARITY_HOSTNAME" \
+ --mail-rcpt "other1@$SINGULARITY_HOSTNAME" \
+ --upload-file - \
+ --user "user:${REGISTER_PASS}" <$CR
+Cc: "other1@$SINGULARITY_HOSTNAME" $CR
+$CR
+To whom it may concern,$CR
+$CR
+Bottom text$CR
+EOF
+) | tee test/smtp_send_email_multi_recipients \
+ | diff <(printf "") /dev/stdin
+
+# Check that sending an email without to field fails
+(
+! curl --url "smtps://$SINGULARITY_HOSTNAME" \
+ --unix-socket ./socks/smtps.sock \
+ "${CURL_OPTS[@]}" \
+ --mail-from "user@$SINGULARITY_HOSTNAME" \
+ --mail-rcpt "other@$SINGULARITY_HOSTNAME" \
+ --mail-rcpt "other1@$SINGULARITY_HOSTNAME" \
+ --upload-file - \
+ --user "user:${REGISTER_PASS}" <$CR
+$CR
+To whom it may concern,$CR
+$CR
+Bottom text$CR
+EOF
+) |& tee test/smtp_send_erroneous_email_wo_to \
+ | grep 'Syntax error in message contents'
+
+
# Verify that no email shows up without the journal being updated
curl --url "pop3s://$SINGULARITY_HOSTNAME" \
--unix-socket ./socks/pop3s.sock \