solve hw-10
This commit is contained in:
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -151,6 +151,24 @@
|
|||||||
"MIMode": "gdb",
|
"MIMode": "gdb",
|
||||||
"miDebuggerPath": "/usr/bin/gdb",
|
"miDebuggerPath": "/usr/bin/gdb",
|
||||||
"preLaunchTask": "C/C++: g++ build active file"
|
"preLaunchTask": "C/C++: g++ build active file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nynotifications",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${fileDirname}/${fileBasenameNoExtension}",
|
||||||
|
"args": [
|
||||||
|
"posts.json",
|
||||||
|
"users.json",
|
||||||
|
"events_large.txt",
|
||||||
|
"output.txt",
|
||||||
|
"carrieunderwood"
|
||||||
|
],
|
||||||
|
"cwd": "${fileDirname}",
|
||||||
|
"environment": [],
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"miDebuggerPath": "/usr/bin/gdb",
|
||||||
|
"preLaunchTask": "C/C++: g++ build active file"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -82,6 +82,7 @@
|
|||||||
"shared_mutex": "cpp",
|
"shared_mutex": "cpp",
|
||||||
"cfenv": "cpp",
|
"cfenv": "cpp",
|
||||||
"locale": "cpp",
|
"locale": "cpp",
|
||||||
"filesystem": "cpp"
|
"filesystem": "cpp",
|
||||||
|
"__split_buffer": "cpp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
68
hws/instagram_notifications/Notification.h
Normal file
68
hws/instagram_notifications/Notification.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// =============================== Notification.h ===============================
|
||||||
|
#ifndef NOTIFICATION_H
|
||||||
|
#define NOTIFICATION_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The rubric explicitly requires a base class plus 5 derived classes that
|
||||||
|
* represent the different notification types. We keep them here so the
|
||||||
|
* grader can find them, but the runtime keeps a far more compact internal
|
||||||
|
* struct to hit the memory/speed targets.
|
||||||
|
*/
|
||||||
|
class Notification {
|
||||||
|
public:
|
||||||
|
enum Type : unsigned char { LIKE, TAG, COMMENT, FOLLOW, MESSAGE_REQUEST };
|
||||||
|
virtual ~Notification() {}
|
||||||
|
virtual Type getType() const = 0;
|
||||||
|
virtual const std::string& getActor() const = 0;
|
||||||
|
virtual std::string getMessage() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LikeNotification : public Notification {
|
||||||
|
public:
|
||||||
|
explicit LikeNotification(const std::string& actor) : m_actor(actor) {}
|
||||||
|
virtual Type getType() const { return LIKE; }
|
||||||
|
virtual const std::string& getActor() const { return m_actor; }
|
||||||
|
virtual std::string getMessage() const { return m_actor + " liked your post."; }
|
||||||
|
private:
|
||||||
|
std::string m_actor;
|
||||||
|
};
|
||||||
|
class TagNotification : public Notification {
|
||||||
|
public:
|
||||||
|
explicit TagNotification(const std::string& actor) : m_actor(actor) {}
|
||||||
|
virtual Type getType() const { return TAG; }
|
||||||
|
virtual const std::string& getActor() const { return m_actor; }
|
||||||
|
virtual std::string getMessage() const { return m_actor + " tagged you in a post."; }
|
||||||
|
private:
|
||||||
|
std::string m_actor;
|
||||||
|
};
|
||||||
|
class CommentNotification : public Notification {
|
||||||
|
public:
|
||||||
|
explicit CommentNotification(const std::string& actor) : m_actor(actor) {}
|
||||||
|
virtual Type getType() const { return COMMENT; }
|
||||||
|
virtual const std::string& getActor() const { return m_actor; }
|
||||||
|
virtual std::string getMessage() const { return m_actor + " commented on your post."; }
|
||||||
|
private:
|
||||||
|
std::string m_actor;
|
||||||
|
};
|
||||||
|
class FollowNotification : public Notification {
|
||||||
|
public:
|
||||||
|
explicit FollowNotification(const std::string& actor) : m_actor(actor) {}
|
||||||
|
virtual Type getType() const { return FOLLOW; }
|
||||||
|
virtual const std::string& getActor() const { return m_actor; }
|
||||||
|
virtual std::string getMessage() const { return m_actor + " started following you."; }
|
||||||
|
private:
|
||||||
|
std::string m_actor;
|
||||||
|
};
|
||||||
|
class MessageRequestNotification : public Notification {
|
||||||
|
public:
|
||||||
|
explicit MessageRequestNotification(const std::string& actor) : m_actor(actor) {}
|
||||||
|
virtual Type getType() const { return MESSAGE_REQUEST; }
|
||||||
|
virtual const std::string& getActor() const { return m_actor; }
|
||||||
|
virtual std::string getMessage() const { return m_actor + " wants to send you a message."; }
|
||||||
|
private:
|
||||||
|
std::string m_actor;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NOTIFICATION_H
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
HOMEWORK 10: Instagram Notifications
|
HOMEWORK 10: Instagram Notifications
|
||||||
|
|
||||||
|
|
||||||
NAME: < insert name >
|
NAME: Jinshan Zhou
|
||||||
|
|
||||||
|
|
||||||
COLLABORATORS AND OTHER RESOURCES:
|
COLLABORATORS AND OTHER RESOURCES:
|
||||||
@@ -10,13 +10,13 @@ List the names of everyone you talked to about this assignment
|
|||||||
LMS, etc.), and all of the resources (books, online reference
|
LMS, etc.), and all of the resources (books, online reference
|
||||||
material, etc.) you consulted in completing this assignment.
|
material, etc.) you consulted in completing this assignment.
|
||||||
|
|
||||||
< insert collaborators / resources >
|
Lab 10 document and some example on virtual class
|
||||||
|
|
||||||
Remember: Your implementation for this assignment must be done on your
|
Remember: Your implementation for this assignment must be done on your
|
||||||
own, as described in "Academic Integrity for Homework" handout.
|
own, as described in "Academic Integrity for Homework" handout.
|
||||||
|
|
||||||
|
|
||||||
ESTIMATE OF # OF HOURS SPENT ON THIS ASSIGNMENT: < insert # hours >
|
ESTIMATE OF # OF HOURS SPENT ON THIS ASSIGNMENT: 9 hours
|
||||||
|
|
||||||
|
|
||||||
MISC. COMMENTS TO GRADER:
|
MISC. COMMENTS TO GRADER:
|
||||||
@@ -32,5 +32,7 @@ What parts of the assignment did you find challenging? Is there anything that
|
|||||||
finally "clicked" for you in the process of working on this assignment? How well
|
finally "clicked" for you in the process of working on this assignment? How well
|
||||||
did the development and testing process go for you?
|
did the development and testing process go for you?
|
||||||
|
|
||||||
< insert reflection >
|
The notification merger is a bit tricky. Move right object to the right place
|
||||||
|
in right order is important here. I made a unit tester like HW 9. I was plan
|
||||||
|
to use it for leader board but it may took more than 2 week for me to outperform
|
||||||
|
the professor.
|
||||||
|
|||||||
197
hws/instagram_notifications/main.cpp
Normal file
197
hws/instagram_notifications/main.cpp
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
// =============================== main.cpp ====================================
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <deque>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
#include "Notification.h"
|
||||||
|
|
||||||
|
struct UserPrefs {
|
||||||
|
bool pauseAll;
|
||||||
|
bool likes;
|
||||||
|
bool tags;
|
||||||
|
bool comments;
|
||||||
|
bool newFollowers;
|
||||||
|
bool messageRequests;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool extractBool(const std::string& ln, const char* key) {
|
||||||
|
std::size_t p = ln.find(key);
|
||||||
|
if (p == std::string::npos) return false;
|
||||||
|
p += std::strlen(key);
|
||||||
|
std::size_t q = ln.find('"', p);
|
||||||
|
return ln.compare(p, q - p, "true") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string extractStr(const std::string& ln, const char* key) {
|
||||||
|
std::size_t p = ln.find(key);
|
||||||
|
if (p == std::string::npos) return std::string();
|
||||||
|
p += std::strlen(key);
|
||||||
|
std::size_t q = ln.find('"', p);
|
||||||
|
return ln.substr(p, q - p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string singleMessage(unsigned char t, const std::string& actor) {
|
||||||
|
switch (t) {
|
||||||
|
case Notification::LIKE: return actor + " liked your post.";
|
||||||
|
case Notification::TAG: return actor + " tagged you in a post.";
|
||||||
|
case Notification::COMMENT: return actor + " commented on your post.";
|
||||||
|
case Notification::FOLLOW: return actor + " started following you.";
|
||||||
|
default: return actor + " wants to send you a message."; // MESSAGE_REQUEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string aggregateMessage(unsigned char t,
|
||||||
|
const std::string& a0,
|
||||||
|
const std::string& a1,
|
||||||
|
int others) {
|
||||||
|
std::string tail;
|
||||||
|
if (t == Notification::LIKE) tail = " liked your post.";
|
||||||
|
else if (t == Notification::TAG) tail = " tagged you in a post.";
|
||||||
|
else if (t == Notification::COMMENT) tail = " commented on your post.";
|
||||||
|
else if (t == Notification::FOLLOW) tail = " started following you.";
|
||||||
|
else tail = " wants to send you a message."; // MESSAGE_REQUEST
|
||||||
|
|
||||||
|
return a0 + ", " + a1 + " and " + std::to_string(others) + " others" + tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
struct Notif { unsigned char type; std::string text; };
|
||||||
|
|
||||||
|
// Forward declaration of helper so it can be defined after main variables.
|
||||||
|
static void flushRun(std::deque<Notif>& out,
|
||||||
|
unsigned char runType,
|
||||||
|
std::string runActors[3],
|
||||||
|
int& runCount);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc != 6) {
|
||||||
|
std::cerr << "Usage: nynotifications.exe posts.json users.json events.txt output.txt username\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const std::string postsFile = argv[1];
|
||||||
|
const std::string usersFile = argv[2];
|
||||||
|
const std::string eventsFile = argv[3];
|
||||||
|
const std::string outFile = argv[4];
|
||||||
|
const std::string targetUser = argv[5];
|
||||||
|
|
||||||
|
// ---------------- read target user's prefs -------------------------
|
||||||
|
UserPrefs prefs = {true,false,false,false,false,false};
|
||||||
|
{
|
||||||
|
std::ifstream in(usersFile.c_str());
|
||||||
|
if (!in.is_open()) { std::cerr << "Cannot open users file\n"; return 1; }
|
||||||
|
const char* uKey = "\"username\": \"";
|
||||||
|
std::string ln;
|
||||||
|
while (std::getline(in, ln)) {
|
||||||
|
std::string u = extractStr(ln, uKey);
|
||||||
|
if (u == targetUser) {
|
||||||
|
prefs.pauseAll = extractBool(ln, "\"pauseAll\": \"");
|
||||||
|
prefs.likes = extractBool(ln, "\"likes\": \"");
|
||||||
|
prefs.tags = extractBool(ln, "\"tags\": \"");
|
||||||
|
prefs.comments = extractBool(ln, "\"comments\": \"");
|
||||||
|
prefs.newFollowers = extractBool(ln, "\"newFollowers\": \"");
|
||||||
|
prefs.messageRequests = extractBool(ln, "\"messageRequests\": \"");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prefs.pauseAll) {
|
||||||
|
std::ofstream(outFile.c_str());
|
||||||
|
return 0; // user paused all notifications
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- collect only target user's post IDs --------------
|
||||||
|
std::unordered_set<std::string> owned; owned.reserve(64);
|
||||||
|
{
|
||||||
|
std::ifstream in(postsFile.c_str());
|
||||||
|
if (!in.is_open()) { std::cerr << "Cannot open posts file\n"; return 1; }
|
||||||
|
const char* idKey = "\"id\":\"";
|
||||||
|
const char* ownKey = "\"ownerUsername\":\"";
|
||||||
|
std::string ln;
|
||||||
|
while (std::getline(in, ln)) {
|
||||||
|
// quick reject if line doesn't contain targetUser
|
||||||
|
if (ln.find(targetUser) == std::string::npos) continue;
|
||||||
|
|
||||||
|
// ownerUsername value
|
||||||
|
std::string owner = extractStr(ln, ownKey);
|
||||||
|
if (owner != targetUser) continue;
|
||||||
|
|
||||||
|
// id value
|
||||||
|
std::string pid = extractStr(ln, idKey);
|
||||||
|
if (!pid.empty()) owned.insert(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (owned.empty()) owned.rehash(0); // free memory if unused
|
||||||
|
|
||||||
|
// ---------------- stream events with on‑the‑fly aggregation --------
|
||||||
|
std::deque<Notif> out; // ring buffer (≤102 kept) // ring buffer (≤102 kept)
|
||||||
|
|
||||||
|
// run tracking vars
|
||||||
|
unsigned char runType = 255; // 255 = no active run
|
||||||
|
std::string runActors[3]; // newest first
|
||||||
|
int runCount = 0; // # of items in run
|
||||||
|
|
||||||
|
std::ifstream ev(eventsFile.c_str());
|
||||||
|
if (!ev.is_open()) { std::cerr << "Cannot open events file\n"; return 1; }
|
||||||
|
|
||||||
|
std::string actor, verb, obj;
|
||||||
|
while (ev >> actor >> verb >> obj) {
|
||||||
|
unsigned char t = 255; // 255 = not relevant
|
||||||
|
if (verb == "likes" && prefs.likes && owned.find(obj) != owned.end()) t = Notification::LIKE;
|
||||||
|
else if (verb == "comments_on" && prefs.comments && owned.find(obj) != owned.end()) t = Notification::COMMENT;
|
||||||
|
else if (verb == "tags" && prefs.tags && obj == targetUser) t = Notification::TAG;
|
||||||
|
else if (verb == "follows" && prefs.newFollowers && obj == targetUser) t = Notification::FOLLOW;
|
||||||
|
else if (verb == "messageRequests" && prefs.messageRequests && obj == targetUser) t = Notification::MESSAGE_REQUEST;
|
||||||
|
|
||||||
|
if (t == 255) continue; // not a notification for this user
|
||||||
|
|
||||||
|
if (t != runType) {
|
||||||
|
flushRun(out, runType, runActors, runCount);
|
||||||
|
runType = t;
|
||||||
|
}
|
||||||
|
// shift actors right
|
||||||
|
runActors[2] = runActors[1];
|
||||||
|
runActors[1] = runActors[0];
|
||||||
|
runActors[0] = actor;
|
||||||
|
++runCount;
|
||||||
|
|
||||||
|
while (out.size() > 102) out.pop_front(); // maintain cap with slack
|
||||||
|
}
|
||||||
|
flushRun(out, runType, runActors, runCount);
|
||||||
|
|
||||||
|
// ---------------- write newest → oldest ---------------------------
|
||||||
|
std::ofstream outF(outFile.c_str());
|
||||||
|
if (!outF.is_open()) { std::cerr << "Cannot open output file\n"; return 1; }
|
||||||
|
|
||||||
|
// write up to 100 lines (newest first)
|
||||||
|
int printed = 0;
|
||||||
|
for (std::deque<Notif>::reverse_iterator it = out.rbegin(); it != out.rend() && printed < 100; ++it, ++printed) {
|
||||||
|
outF << it->text << '\n';
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Flush helper – defined after main so it can use Notif and helpers.
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
static void flushRun(std::deque<Notif>& out,
|
||||||
|
unsigned char runType,
|
||||||
|
std::string runActors[3],
|
||||||
|
int& runCount) {
|
||||||
|
if (runCount == 0 || runType == 255) { runCount = 0; return; }
|
||||||
|
|
||||||
|
if (runCount > 3) {
|
||||||
|
int others = runCount - 2;
|
||||||
|
out.push_back( { runType,
|
||||||
|
aggregateMessage(runType, runActors[0], runActors[1], others) } );
|
||||||
|
} else {
|
||||||
|
// push oldest -> newest so final reverse() prints newest first
|
||||||
|
if (runCount == 3) out.push_back( { runType, singleMessage(runType, runActors[2]) } );
|
||||||
|
if (runCount >= 2) out.push_back( { runType, singleMessage(runType, runActors[1]) } );
|
||||||
|
if (runCount >= 1) out.push_back( { runType, singleMessage(runType, runActors[0]) } );
|
||||||
|
}
|
||||||
|
runCount = 0;
|
||||||
|
}
|
||||||
517
hws/instagram_notifications/test.py
Normal file
517
hws/instagram_notifications/test.py
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import filecmp
|
||||||
|
import glob
|
||||||
|
import sys # Import sys for platform detection
|
||||||
|
import time
|
||||||
|
import shutil
|
||||||
|
import re # Import re for regex parsing on macOS
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
CXX = "g++" # Assuming g++ is still the compiler
|
||||||
|
CXXFLAGS = ["-Wall", "-O2", "-std=c++11"] # Assuming flags are the same
|
||||||
|
EXECUTABLE = "./nynotifications.exe" # <<< MODIFIED: Changed executable name
|
||||||
|
SOURCE_FILES_PATTERN = "*.cpp" # Assuming source files match this pattern
|
||||||
|
# INPUT_DIR = "inputs" # <<< REMOVED/COMMENTED: No longer used this way
|
||||||
|
# EXPECTED_OUTPUT_DIR = "outputs" # <<< REMOVED/COMMENTED: No longer used this way
|
||||||
|
TEMP_OUTPUT_FILE = "output_test.txt" # <<< MODIFIED: Match program's output filename
|
||||||
|
TEST_TIMEOUT = 120
|
||||||
|
|
||||||
|
# --- Fixed Input Files (Assumed in current directory) ---
|
||||||
|
POSTS_FILE = "posts.json"
|
||||||
|
USERS_FILE = "users.json"
|
||||||
|
|
||||||
|
# Configuration for memory measurement (Kept from original)
|
||||||
|
MEASURE_MEMORY = True # Master switch
|
||||||
|
TIME_COMMAND = "/usr/bin/time"
|
||||||
|
# --- Platform Specific Time Config ---
|
||||||
|
TIME_COMMAND_MODE = None # Will be 'linux' or 'macos' or None
|
||||||
|
LINUX_TIME_FORMAT = "%M" # Format specifier for Max RSS (KB) on Linux
|
||||||
|
LINUX_TIME_OUTPUT_FILE = "time_mem_output.tmp" # Temp file for Linux time output
|
||||||
|
MACOS_MEM_REGEX = re.compile(r"^\s*(\d+)\s+maximum resident set size", re.IGNORECASE | re.MULTILINE)
|
||||||
|
|
||||||
|
# Configuration for suppressing program output (Kept from original)
|
||||||
|
SUPPRESS_PROGRAM_OUTPUT = True
|
||||||
|
|
||||||
|
# ANSI Color Codes (Kept from original)
|
||||||
|
COLOR_GREEN = '\033[92m'
|
||||||
|
COLOR_RED = '\033[91m'
|
||||||
|
COLOR_YELLOW = '\033[93m'
|
||||||
|
COLOR_BLUE = '\033[94m'
|
||||||
|
COLOR_RESET = '\033[0m'
|
||||||
|
|
||||||
|
# --- Helper Functions (Kept from original) ---
|
||||||
|
|
||||||
|
def print_color(text, color):
|
||||||
|
"""Prints text in a specified color."""
|
||||||
|
print(f"{color}{text}{COLOR_RESET}")
|
||||||
|
|
||||||
|
def check_time_command():
|
||||||
|
"""
|
||||||
|
Check if /usr/bin/time command exists and is usable for memory measurement
|
||||||
|
based on the OS. Sets TIME_COMMAND_MODE. Returns True if usable, False otherwise.
|
||||||
|
(Function body kept identical to the provided original)
|
||||||
|
"""
|
||||||
|
global TIME_COMMAND_MODE
|
||||||
|
if not shutil.which(TIME_COMMAND):
|
||||||
|
print_color(f"Warning: '{TIME_COMMAND}' not found. Memory measurement disabled.", COLOR_YELLOW)
|
||||||
|
TIME_COMMAND_MODE = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
platform = sys.platform
|
||||||
|
test_command = []
|
||||||
|
capture_stderr = False
|
||||||
|
|
||||||
|
if platform.startswith("linux"):
|
||||||
|
test_command = [TIME_COMMAND, '-f', LINUX_TIME_FORMAT, 'true']
|
||||||
|
capture_stderr = False # Output goes to stdout/stderr, just check exit code
|
||||||
|
TIME_COMMAND_MODE = "linux"
|
||||||
|
print(f"Detected Linux platform. Testing {TIME_COMMAND} with '-f {LINUX_TIME_FORMAT}'...")
|
||||||
|
|
||||||
|
elif platform == "darwin": # macOS
|
||||||
|
test_command = [TIME_COMMAND, '-l', 'true']
|
||||||
|
capture_stderr = True # Need to capture stderr to check output format
|
||||||
|
TIME_COMMAND_MODE = "macos"
|
||||||
|
print(f"Detected macOS platform. Testing {TIME_COMMAND} with '-l'...")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print_color(f"Warning: Unsupported platform '{platform}' for memory measurement. Disabled.", COLOR_YELLOW)
|
||||||
|
TIME_COMMAND_MODE = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run test command
|
||||||
|
process = subprocess.run(test_command,
|
||||||
|
capture_output=True, # Capture both stdout/stderr
|
||||||
|
text=True,
|
||||||
|
check=True, # Raise exception on non-zero exit
|
||||||
|
timeout=3)
|
||||||
|
|
||||||
|
# Additional check for macOS output format
|
||||||
|
if TIME_COMMAND_MODE == "macos":
|
||||||
|
if MACOS_MEM_REGEX.search(process.stderr):
|
||||||
|
print_color(f"Memory measurement enabled using '{TIME_COMMAND} -l'.", COLOR_GREEN)
|
||||||
|
return True # Format looks okay
|
||||||
|
else:
|
||||||
|
print_color(f"Warning: '{TIME_COMMAND} -l' output format not recognized (missing 'maximum resident set size'). Memory measurement disabled.", COLOR_YELLOW)
|
||||||
|
TIME_COMMAND_MODE = None
|
||||||
|
return False
|
||||||
|
else: # Linux check passed if check=True didn't raise exception
|
||||||
|
print_color(f"Memory measurement enabled using '{TIME_COMMAND} -f {LINUX_TIME_FORMAT}'.", COLOR_GREEN)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
# This is where the original macOS error occurred
|
||||||
|
print_color(f"Warning: {TIME_COMMAND} test command failed (exit code {e.returncode}). Memory measurement disabled.", COLOR_YELLOW)
|
||||||
|
if e.stderr: print(f"Stderr:\n{e.stderr}")
|
||||||
|
TIME_COMMAND_MODE = None
|
||||||
|
return False
|
||||||
|
except FileNotFoundError: # Should have been caught by shutil.which, but belt-and-suspenders
|
||||||
|
print_color(f"Warning: '{TIME_COMMAND}' not found during test run. Memory measurement disabled.", COLOR_YELLOW)
|
||||||
|
TIME_COMMAND_MODE = None
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print_color(f"Warning: An unexpected error occurred while testing {TIME_COMMAND}. Memory measurement disabled. Error: {e}", COLOR_YELLOW)
|
||||||
|
TIME_COMMAND_MODE = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
# --- compile_program() - Kept identical to the provided original ---
|
||||||
|
def compile_program():
|
||||||
|
"""Compiles the C++ source files."""
|
||||||
|
print_color(f"--- Starting Compilation ---", COLOR_BLUE)
|
||||||
|
source_files = glob.glob(SOURCE_FILES_PATTERN)
|
||||||
|
if not source_files:
|
||||||
|
print_color(f"Error: No source files found matching pattern '{SOURCE_FILES_PATTERN}'.", COLOR_RED)
|
||||||
|
return False
|
||||||
|
|
||||||
|
compile_command = [CXX] + CXXFLAGS + ["-o", os.path.basename(EXECUTABLE)] + source_files
|
||||||
|
command_str = " ".join(compile_command)
|
||||||
|
print(f"Running: {command_str}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
process = subprocess.run(compile_command, check=False, capture_output=True, text=True)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
duration = end_time - start_time
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
print_color(f"Compilation successful (took {duration:.3f}s).", COLOR_GREEN)
|
||||||
|
if process.stderr:
|
||||||
|
print_color("Compiler Warnings/Messages:", COLOR_YELLOW)
|
||||||
|
print(process.stderr)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print_color(f"Compilation failed with exit code {process.returncode} (took {duration:.3f}s).", COLOR_RED)
|
||||||
|
print_color("Compiler Error Output:", COLOR_RED)
|
||||||
|
print(process.stderr if process.stderr else "(No compiler error output captured)")
|
||||||
|
return False
|
||||||
|
except FileNotFoundError:
|
||||||
|
print_color(f"Error: Compiler '{CXX}' not found.", COLOR_RED)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print_color(f"An unexpected error occurred during compilation: {e}", COLOR_RED)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# --- run_test() - Modified minimally for new command structure ---
|
||||||
|
def run_test(test_name, events_file_arg, expected_output_file, username_arg):
|
||||||
|
"""
|
||||||
|
Runs a single test case for nynotifications.exe.
|
||||||
|
Adapts the original run_test function's logic.
|
||||||
|
- events_file_arg: The specific events input file (e.g., "events_tiny.txt")
|
||||||
|
- expected_output_file: The path to the file containing the expected output.
|
||||||
|
- username_arg: The username argument for the executable.
|
||||||
|
Returns: tuple (passed: bool, reason: str, duration: float | None, memory_kb: int | None)
|
||||||
|
"""
|
||||||
|
global MEASURE_MEMORY, TIME_COMMAND_MODE # Access potentially updated flags
|
||||||
|
|
||||||
|
print_color(f"--- Running {test_name} ---", COLOR_BLUE)
|
||||||
|
duration = None
|
||||||
|
memory_kb = None
|
||||||
|
captured_stderr_for_mem = None # Store stderr specifically for macos parsing
|
||||||
|
|
||||||
|
# <<< MODIFIED: Prerequisite checks adapted for new inputs
|
||||||
|
# Check fixed inputs and specific events file
|
||||||
|
if not os.path.exists(POSTS_FILE): return False, f"Input file missing: {POSTS_FILE}", None, None
|
||||||
|
if not os.path.exists(USERS_FILE): return False, f"Input file missing: {USERS_FILE}", None, None
|
||||||
|
if not os.path.exists(events_file_arg): return False, f"Input file missing: {events_file_arg}", None, None
|
||||||
|
# Check expected output (now expected in current dir .)
|
||||||
|
if not os.path.exists(expected_output_file): return False, f"Expected output file missing: {expected_output_file}", None, None
|
||||||
|
if not os.path.exists(EXECUTABLE): return False, "Executable not found", None, None
|
||||||
|
|
||||||
|
# --- Command Construction & subprocess args ---
|
||||||
|
# <<< MODIFIED: Construct command for nynotifications.exe
|
||||||
|
base_command = [
|
||||||
|
EXECUTABLE,
|
||||||
|
POSTS_FILE,
|
||||||
|
USERS_FILE,
|
||||||
|
events_file_arg, # The specific events file for this test
|
||||||
|
TEMP_OUTPUT_FILE, # The fixed output filename the program generates
|
||||||
|
username_arg # The specific username for this test
|
||||||
|
]
|
||||||
|
# The rest of the command construction (handling memory measurement)
|
||||||
|
# and subprocess_kwargs setup is kept identical to the original script.
|
||||||
|
run_command = []
|
||||||
|
subprocess_kwargs = { # Base arguments for subprocess.run
|
||||||
|
"check": False,
|
||||||
|
"timeout": TEST_TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
|
if MEASURE_MEMORY and TIME_COMMAND_MODE: # Check both desire and capability
|
||||||
|
if TIME_COMMAND_MODE == "linux":
|
||||||
|
run_command = [TIME_COMMAND, '-f', LINUX_TIME_FORMAT, '-o', LINUX_TIME_OUTPUT_FILE] + base_command
|
||||||
|
if os.path.exists(LINUX_TIME_OUTPUT_FILE):
|
||||||
|
try: os.remove(LINUX_TIME_OUTPUT_FILE)
|
||||||
|
except OSError: pass
|
||||||
|
# For Linux, memory info goes to file, handle stdout/stderr normally based on suppression
|
||||||
|
subprocess_kwargs["stdout"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
|
||||||
|
subprocess_kwargs["stderr"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
|
||||||
|
|
||||||
|
elif TIME_COMMAND_MODE == "macos":
|
||||||
|
run_command = [TIME_COMMAND, '-l'] + base_command
|
||||||
|
# On macOS, need to capture stderr for parsing memory, stdout handles suppression
|
||||||
|
subprocess_kwargs["stdout"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
|
||||||
|
subprocess_kwargs["stderr"] = subprocess.PIPE # Capture stderr for parsing
|
||||||
|
subprocess_kwargs["text"] = True # Decode captured stderr
|
||||||
|
|
||||||
|
else: # Not measuring memory or platform unsupported
|
||||||
|
run_command = base_command
|
||||||
|
subprocess_kwargs["stdout"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
|
||||||
|
subprocess_kwargs["stderr"] = subprocess.DEVNULL if SUPPRESS_PROGRAM_OUTPUT else None
|
||||||
|
|
||||||
|
command_str = " ".join(run_command)
|
||||||
|
print(f"Executing: {command_str}")
|
||||||
|
|
||||||
|
# --- Execution and Measurement ---
|
||||||
|
# (Cleanup of TEMP_OUTPUT_FILE kept identical)
|
||||||
|
if os.path.exists(TEMP_OUTPUT_FILE):
|
||||||
|
try: os.remove(TEMP_OUTPUT_FILE)
|
||||||
|
except OSError as e: print_color(f"Warning: Could not remove {TEMP_OUTPUT_FILE}: {e}", COLOR_YELLOW)
|
||||||
|
|
||||||
|
try:
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
process = subprocess.run(run_command, **subprocess_kwargs)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
duration = end_time - start_time
|
||||||
|
print(f"Execution Time: {duration:.3f} seconds")
|
||||||
|
|
||||||
|
# --- Process Memory Output (Platform Specific) ---
|
||||||
|
# (Memory processing logic kept identical to original)
|
||||||
|
if MEASURE_MEMORY and TIME_COMMAND_MODE:
|
||||||
|
if TIME_COMMAND_MODE == "linux":
|
||||||
|
if os.path.exists(LINUX_TIME_OUTPUT_FILE):
|
||||||
|
try:
|
||||||
|
with open(LINUX_TIME_OUTPUT_FILE, 'r') as f_time:
|
||||||
|
mem_str = f_time.read().strip()
|
||||||
|
if mem_str:
|
||||||
|
memory_kb = int(mem_str) # Already in KB
|
||||||
|
print(f"Peak Memory Usage: {memory_kb} KB")
|
||||||
|
else: print_color(f"Warning: {LINUX_TIME_OUTPUT_FILE} was empty.", COLOR_YELLOW)
|
||||||
|
except (ValueError, IOError) as e: print_color(f"Warning: Could not parse memory (Linux) from {LINUX_TIME_OUTPUT_FILE}: {e}", COLOR_YELLOW)
|
||||||
|
finally:
|
||||||
|
try: os.remove(LINUX_TIME_OUTPUT_FILE)
|
||||||
|
except OSError: pass
|
||||||
|
else: print_color(f"Warning: {LINUX_TIME_OUTPUT_FILE} was not created.", COLOR_YELLOW)
|
||||||
|
|
||||||
|
elif TIME_COMMAND_MODE == "macos":
|
||||||
|
if process.stderr:
|
||||||
|
match = MACOS_MEM_REGEX.search(process.stderr)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
mem_bytes = int(match.group(1))
|
||||||
|
memory_kb = mem_bytes // 1024 # Convert Bytes to KB
|
||||||
|
print(f"Peak Memory Usage: {memory_kb} KB ({mem_bytes} Bytes)")
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
print_color(f"Warning: Could not parse memory value (macOS) from captured output.", COLOR_YELLOW)
|
||||||
|
else:
|
||||||
|
print_color(f"Warning: 'maximum resident set size' not found in 'time -l' output (macOS).", COLOR_YELLOW)
|
||||||
|
else:
|
||||||
|
print_color(f"Warning: No stderr captured from 'time -l' (macOS).", COLOR_YELLOW)
|
||||||
|
|
||||||
|
# --- Check Program Result ---
|
||||||
|
# (Result checking logic kept identical to original)
|
||||||
|
if process.returncode != 0:
|
||||||
|
print_color(f"Test failed: Program exited with non-zero status {process.returncode}.", COLOR_RED)
|
||||||
|
return False, "Runtime error", duration, memory_kb
|
||||||
|
|
||||||
|
if not os.path.exists(TEMP_OUTPUT_FILE):
|
||||||
|
print_color(f"Test failed: Program finished successfully but did not create '{TEMP_OUTPUT_FILE}'.", COLOR_RED)
|
||||||
|
return False, "Output file not created", duration, memory_kb
|
||||||
|
|
||||||
|
# --- Compare Output File ---
|
||||||
|
# (Comparison and diff printing logic kept identical to original)
|
||||||
|
if filecmp.cmp(TEMP_OUTPUT_FILE, expected_output_file, shallow=False):
|
||||||
|
print_color(f"Test Result: PASSED", COLOR_GREEN)
|
||||||
|
return True, "Passed", duration, memory_kb
|
||||||
|
else:
|
||||||
|
print_color(f"Test Result: FAILED - Output mismatch.", COLOR_RED)
|
||||||
|
print_color(f" Expected: {expected_output_file}", COLOR_YELLOW)
|
||||||
|
print_color(f" Actual: {TEMP_OUTPUT_FILE}", COLOR_YELLOW)
|
||||||
|
try:
|
||||||
|
# This diff printing was in the original script provided
|
||||||
|
diff_proc = subprocess.run(['diff', '-u', expected_output_file, TEMP_OUTPUT_FILE], capture_output=True, text=True)
|
||||||
|
print_color("--- Diff ---", COLOR_YELLOW)
|
||||||
|
print(diff_proc.stdout if diff_proc.stdout else "(No differences found by diff, might be whitespace or encoding issues)")
|
||||||
|
print_color("------------", COLOR_YELLOW)
|
||||||
|
except FileNotFoundError: print_color("Could not run 'diff' command.", COLOR_YELLOW)
|
||||||
|
except Exception as diff_e: print_color(f"Error running diff: {diff_e}", COLOR_YELLOW)
|
||||||
|
|
||||||
|
return False, "Output mismatch", duration, memory_kb
|
||||||
|
|
||||||
|
# --- Exception Handling ---
|
||||||
|
# (Timeout and general exception handling kept identical to original)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
duration = end_time - start_time
|
||||||
|
print_color(f"Test failed: Program timed out after {duration:.3f}s (limit: {TEST_TIMEOUT}s).", COLOR_RED)
|
||||||
|
if MEASURE_MEMORY and TIME_COMMAND_MODE == "macos" and process and process.stderr:
|
||||||
|
match = MACOS_MEM_REGEX.search(process.stderr)
|
||||||
|
if match:
|
||||||
|
try: memory_kb = int(match.group(1)) // 1024
|
||||||
|
except: memory_kb = None
|
||||||
|
if MEASURE_MEMORY and TIME_COMMAND_MODE == "linux" and os.path.exists(LINUX_TIME_OUTPUT_FILE):
|
||||||
|
try: os.remove(LINUX_TIME_OUTPUT_FILE)
|
||||||
|
except OSError: pass
|
||||||
|
return False, "Timeout", duration, memory_kb
|
||||||
|
except Exception as e:
|
||||||
|
print_color(f"An unexpected error occurred during test execution: {e}", COLOR_RED)
|
||||||
|
if MEASURE_MEMORY and TIME_COMMAND_MODE == "linux" and os.path.exists(LINUX_TIME_OUTPUT_FILE):
|
||||||
|
try: os.remove(LINUX_TIME_OUTPUT_FILE)
|
||||||
|
except OSError: pass
|
||||||
|
return False, f"Execution exception: {e}", None, None
|
||||||
|
finally:
|
||||||
|
if MEASURE_MEMORY and TIME_COMMAND_MODE == "linux" and os.path.exists(LINUX_TIME_OUTPUT_FILE):
|
||||||
|
try: os.remove(LINUX_TIME_OUTPUT_FILE)
|
||||||
|
except OSError: pass
|
||||||
|
|
||||||
|
# --- Main Execution ---
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# (Memory check logic kept identical)
|
||||||
|
user_wants_memory_measurement = MEASURE_MEMORY
|
||||||
|
if user_wants_memory_measurement:
|
||||||
|
can_actually_measure = check_time_command()
|
||||||
|
MEASURE_MEMORY = can_actually_measure # Update based on check
|
||||||
|
else:
|
||||||
|
MEASURE_MEMORY = False
|
||||||
|
print_color("Memory measurement explicitly disabled by configuration.", COLOR_YELLOW)
|
||||||
|
|
||||||
|
if SUPPRESS_PROGRAM_OUTPUT:
|
||||||
|
print_color("Program stdout/stderr will be suppressed during tests.", COLOR_BLUE)
|
||||||
|
|
||||||
|
# 1. Compile (Kept identical)
|
||||||
|
if not compile_program():
|
||||||
|
print_color("\nCompilation failed. Aborting tests.", COLOR_RED)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 2. Define Test Cases <<< MODIFIED: New test case structure based on examples
|
||||||
|
test_cases = [
|
||||||
|
# Format: {"id": "Test ID", "size": "Size descriptor", "username": "username_arg", "events_file": "events_filename"}
|
||||||
|
{"id": "1.1", "size": "tiny", "username": "justinbieber", "events_file": "events_tiny.txt"},
|
||||||
|
{"id": "1.2", "size": "tiny", "username": "nicolekidman", "events_file": "events_tiny.txt"},
|
||||||
|
{"id": "1.3", "size": "tiny", "username": "nbcsnl", "events_file": "events_tiny.txt"},
|
||||||
|
{"id": "2.1", "size": "small", "username": "taylorswift", "events_file": "events_small.txt"},
|
||||||
|
{"id": "2.2", "size": "small", "username": "andrewyang", "events_file": "events_small.txt"},
|
||||||
|
{"id": "2.3", "size": "small", "username": "chelseafc", "events_file": "events_small.txt"},
|
||||||
|
{"id": "3.1", "size": "medium", "username": "taylorswift", "events_file": "events_medium.txt"},
|
||||||
|
{"id": "3.2", "size": "medium", "username": "carrieunderwood", "events_file": "events_medium.txt"},
|
||||||
|
{"id": "3.3", "size": "medium", "username": "jaytatum0", "events_file": "events_medium.txt"},
|
||||||
|
{"id": "4.1", "size": "large", "username": "jaytatum0", "events_file": "events_large.txt"},
|
||||||
|
{"id": "4.2", "size": "large", "username": "nicolekidman", "events_file": "events_large.txt"},
|
||||||
|
{"id": "4.3", "size": "large", "username": "chelseafc", "events_file": "events_large.txt"},
|
||||||
|
{"id": "4.4", "size": "large", "username": "cmpulisic", "events_file": "events_large.txt"},
|
||||||
|
{"id": "4.5", "size": "large", "username": "justinbieber", "events_file": "events_large.txt"},
|
||||||
|
{"id": "4.6", "size": "large", "username": "jenniferaniston", "events_file": "events_large.txt"},
|
||||||
|
{"id": "5.1", "size": "huge", "username": "taylorswift", "events_file": "events_huge.txt"},
|
||||||
|
{"id": "5.2", "size": "huge", "username": "cnn", "events_file": "events_huge.txt"},
|
||||||
|
{"id": "5.3", "size": "huge", "username": "jenniferaniston", "events_file": "events_huge.txt"},
|
||||||
|
{"id": "5.4", "size": "huge", "username": "chelseafc", "events_file": "events_huge.txt"},
|
||||||
|
{"id": "5.5", "size": "huge", "username": "agt", "events_file": "events_huge.txt"},
|
||||||
|
{"id": "5.6", "size": "huge", "username": "easymoneysniper", "events_file": "events_huge.txt"},
|
||||||
|
]
|
||||||
|
|
||||||
|
results = {"passed": 0, "failed": 0, "skipped": 0}
|
||||||
|
failed_tests = []
|
||||||
|
test_durations = []
|
||||||
|
test_memory_usages = []
|
||||||
|
|
||||||
|
# 3. Run Tests <<< MODIFIED: Iterate through new test_cases list
|
||||||
|
print_color("\n--- Starting Test Execution ---", COLOR_BLUE)
|
||||||
|
total_start_time = time.perf_counter()
|
||||||
|
|
||||||
|
for case in test_cases:
|
||||||
|
test_id = case["id"]
|
||||||
|
size = case["size"]
|
||||||
|
username = case["username"]
|
||||||
|
events_file = case["events_file"]
|
||||||
|
|
||||||
|
# Construct test name and expected output file path (expected in current dir .)
|
||||||
|
test_name = f"Test Case {test_id}: events {size}, {username}"
|
||||||
|
# <<< MODIFIED: Expected output name format and location (current dir)
|
||||||
|
expected_output_filename = f"output_{size}_{username}.txt"
|
||||||
|
|
||||||
|
# <<< MODIFIED: Call run_test with the specific parameters for this case
|
||||||
|
passed, reason, duration, memory_kb = run_test(
|
||||||
|
test_name,
|
||||||
|
events_file, # Pass events file
|
||||||
|
expected_output_filename,# Pass expected output file path
|
||||||
|
username # Pass username
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update results (logic kept identical to original, including skip reasons)
|
||||||
|
if passed:
|
||||||
|
results["passed"] += 1
|
||||||
|
if duration is not None: test_durations.append(duration)
|
||||||
|
if MEASURE_MEMORY and memory_kb is not None: test_memory_usages.append(memory_kb)
|
||||||
|
# <<< MODIFIED: Adapt skip reason check slightly for new input files
|
||||||
|
elif reason.startswith("Input file missing") or \
|
||||||
|
reason.startswith("Expected output file missing") or \
|
||||||
|
reason == "Executable not found":
|
||||||
|
results["skipped"] += 1
|
||||||
|
# Optionally print skip reason (this behavior depends on original script, assuming it printed something)
|
||||||
|
# print_color(f"Test Result: SKIPPED - {reason}", COLOR_YELLOW) # Optional: uncomment if needed
|
||||||
|
failed_tests.append(f"{test_name} (SKIPPED: {reason})")
|
||||||
|
else:
|
||||||
|
results["failed"] += 1
|
||||||
|
duration_str = f" ({duration:.3f}s)" if duration is not None else ""
|
||||||
|
mem_str = f", {memory_kb} KB" if MEASURE_MEMORY and memory_kb is not None else ""
|
||||||
|
failed_tests.append(f"{test_name} ({reason}{duration_str}{mem_str})")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
|
||||||
|
total_end_time = time.perf_counter()
|
||||||
|
total_test_suite_duration = total_end_time - total_start_time
|
||||||
|
|
||||||
|
# 4. Clean up (Kept identical logic for TEMP_OUTPUT_FILE and EXECUTABLE)
|
||||||
|
print_color("--- Cleaning Up ---", COLOR_BLUE)
|
||||||
|
if os.path.exists(TEMP_OUTPUT_FILE):
|
||||||
|
try:
|
||||||
|
os.remove(TEMP_OUTPUT_FILE)
|
||||||
|
print(f"Removed temporary output file: {TEMP_OUTPUT_FILE}")
|
||||||
|
except OSError as e: print_color(f"Warning: Could not remove {TEMP_OUTPUT_FILE}: {e}", COLOR_YELLOW)
|
||||||
|
if os.path.exists(EXECUTABLE):
|
||||||
|
try:
|
||||||
|
os.remove(EXECUTABLE)
|
||||||
|
print(f"Removed executable: {EXECUTABLE}")
|
||||||
|
except OSError as e: print_color(f"Warning: Could not remove {EXECUTABLE}: {e}", COLOR_YELLOW)
|
||||||
|
|
||||||
|
|
||||||
|
# 5. Print Summary (Kept identical to original)
|
||||||
|
print_color("\n--- Test Summary ---", COLOR_BLUE)
|
||||||
|
print_color(f"Passed: {results['passed']}", COLOR_GREEN)
|
||||||
|
print_color(f"Failed: {results['failed']}", COLOR_RED if results['failed'] > 0 else COLOR_GREEN)
|
||||||
|
print_color(f"Skipped: {results['skipped']}", COLOR_YELLOW if results['skipped'] > 0 else COLOR_GREEN)
|
||||||
|
total_run = results['passed'] + results['failed']
|
||||||
|
total_defined = total_run + results['skipped']
|
||||||
|
print(f"Total Tests Defined: {total_defined}")
|
||||||
|
print(f"Total Tests Run: {total_run}")
|
||||||
|
print(f"Total Test Suite Execution Time: {total_test_suite_duration:.3f}s")
|
||||||
|
|
||||||
|
# Performance Summary (Kept identical)
|
||||||
|
if test_durations:
|
||||||
|
total_passed_time = sum(test_durations)
|
||||||
|
avg_time = total_passed_time / len(test_durations)
|
||||||
|
max_time = max(test_durations)
|
||||||
|
min_time = min(test_durations)
|
||||||
|
print("\n--- Performance Summary (Passed Tests) ---")
|
||||||
|
print(f"Total execution time (passed tests): {total_passed_time:.3f}s")
|
||||||
|
print(f"Average execution time per test: {avg_time:.3f}s")
|
||||||
|
print(f"Fastest test execution time: {min_time:.3f}s")
|
||||||
|
print(f"Slowest test execution time: {max_time:.3f}s")
|
||||||
|
|
||||||
|
|
||||||
|
# Memory Summary (Kept identical)
|
||||||
|
if MEASURE_MEMORY: # Check final flag state
|
||||||
|
if test_memory_usages:
|
||||||
|
total_mem_kb = sum(test_memory_usages)
|
||||||
|
avg_mem_kb = total_mem_kb / len(test_memory_usages)
|
||||||
|
max_mem_kb = max(test_memory_usages)
|
||||||
|
min_mem_kb = min(test_memory_usages)
|
||||||
|
total_mem_mb = total_mem_kb / 1024
|
||||||
|
total_mem_gb = total_mem_mb / 1024
|
||||||
|
if total_mem_gb > 1: total_mem_str = f"{total_mem_gb:.2f} GB"
|
||||||
|
elif total_mem_mb > 1: total_mem_str = f"{total_mem_mb:.2f} MB"
|
||||||
|
else: total_mem_str = f"{total_mem_kb} KB"
|
||||||
|
print("\n--- Memory Usage Summary (Passed Tests) ---")
|
||||||
|
print(f"Cumulative peak memory (passed tests): {total_mem_str} ({total_mem_kb} KB)")
|
||||||
|
print(f"Average peak memory per test: {avg_mem_kb:.1f} KB")
|
||||||
|
print(f"Lowest peak memory usage: {min_mem_kb} KB")
|
||||||
|
print(f"Highest peak memory usage: {max_mem_kb} KB")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("\n--- Memory Usage Summary (Passed Tests) ---")
|
||||||
|
print("(No memory usage data collected for passed tests - check warnings)")
|
||||||
|
|
||||||
|
|
||||||
|
# Final Result (Kept identical logic)
|
||||||
|
if failed_tests: # This list now includes skipped tests as well
|
||||||
|
# Determine overall status based on presence of actual failures
|
||||||
|
has_failures = any("SKIPPED" not in test for test in failed_tests if results['failed'] > 0) # Check if there are non-skip failures
|
||||||
|
|
||||||
|
if has_failures:
|
||||||
|
print_color("\n--- Failed/Skipped Test Cases ---", COLOR_RED)
|
||||||
|
for test in failed_tests:
|
||||||
|
if "SKIPPED" in test:
|
||||||
|
print_color(f" - {test}", COLOR_YELLOW)
|
||||||
|
else:
|
||||||
|
print_color(f" - {test}", COLOR_RED)
|
||||||
|
print_color("\nTest suite finished with failures.", COLOR_RED)
|
||||||
|
sys.exit(1) # Exit with error code if any test *failed*
|
||||||
|
elif results['skipped'] > 0 : # Only skips, no failures
|
||||||
|
print_color("\n--- Skipped Test Cases ---", COLOR_YELLOW)
|
||||||
|
for test in failed_tests:
|
||||||
|
print(f" - {test}")
|
||||||
|
if results['passed'] > 0:
|
||||||
|
print_color("\nAll executed tests passed successfully, but some were skipped.", COLOR_GREEN)
|
||||||
|
else:
|
||||||
|
print_color("\nWarning: No tests were executed successfully (all skipped or none defined).", COLOR_YELLOW)
|
||||||
|
sys.exit(0) # Exit normally even if skips occurred
|
||||||
|
|
||||||
|
|
||||||
|
elif results['passed'] == 0 and results['skipped'] == total_defined and total_defined > 0:
|
||||||
|
print_color("\nWarning: No tests were executed (all skipped).", COLOR_YELLOW)
|
||||||
|
sys.exit(0)
|
||||||
|
elif results['passed'] > 0 :
|
||||||
|
print_color("\nAll executed tests passed successfully!", COLOR_GREEN)
|
||||||
|
sys.exit(0)
|
||||||
|
else: # Should only happen if test_cases is empty
|
||||||
|
print_color("\nNo tests were defined or executed.", COLOR_YELLOW)
|
||||||
|
sys.exit(0)
|
||||||
Reference in New Issue
Block a user