Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
config==0.5.1
metacall==0.5.0
PyYAML==6.0.2
jsonschema==4.17.3
32 changes: 29 additions & 3 deletions testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,34 @@ def parse_arguments():
default=["cli"],
help="Environments to run the tests on (cli, faas).",
)
parser.add_argument(
"--log-file",
help="Path to log file (default: logs/testing_center.log)",
)
parser.add_argument(
"--log-max-bytes",
type=int,
default=10*1024*1024,
help="Maximum log file size in bytes before rotation (default: 10MB)",
)
parser.add_argument(
"--log-backup-count",
type=int,
default=5,
help="Number of backup log files to keep (default: 5)",
)
return parser.parse_args()


def setup_logger(verbose):
"""Setup logger with the appropriate logging level"""
def setup_logger(verbose, log_file=None, log_max_bytes=None, log_backup_count=None):
"""Setup logger with the appropriate logging level and configuration"""
logger = Logger.get_instance()
logger.set_level("DEBUG" if verbose else "INFO")

# Configure logger with rotation parameters if provided
if log_file or log_max_bytes or log_backup_count:
logger.configure_from_args(log_file, log_max_bytes, log_backup_count)

return logger


Expand Down Expand Up @@ -70,7 +91,12 @@ def run_tests(envs, test_suites):

def main():
args = parse_arguments()
logger = setup_logger(args.verbose)
logger = setup_logger(
args.verbose,
args.log_file,
args.log_max_bytes,
args.log_backup_count
)

try:
project_name, project_path, repo_url, test_suites = extract_test_suites(
Expand Down
65 changes: 59 additions & 6 deletions testing/logger.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,56 @@
import logging
import logging.handlers
import os
from datetime import datetime


class Logger:
"""Singleton class to manage the logging"""

_instance = None

def __init__(self):
def __init__(self, log_file=None, max_bytes=10*1024*1024, backup_count=5):
if Logger._instance is not None:
raise Exception("This class is a singleton!")
else:
Logger._instance = self
self.logger = logging.getLogger("CLI_Tool")
self.level = "INFO" # default level
handler = logging.StreamHandler()
formatter = logging.Formatter("%(levelname)s - %(message)s")
handler.setFormatter(formatter)
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
self.log_file = log_file or os.path.join("logs", "testing_center.log")
self.max_bytes = max_bytes
self.backup_count = backup_count

# Create logs directory if it doesn't exist
os.makedirs(os.path.dirname(self.log_file), exist_ok=True)

# Setup handlers
self._setup_handlers()

def _setup_handlers(self):
"""Setup console and file handlers with rotation"""
# Clear existing handlers
self.logger.handlers.clear()

# Console handler with colors
console_handler = logging.StreamHandler()
console_formatter = logging.Formatter("%(levelname)s - %(message)s")
console_handler.setFormatter(console_formatter)

# File handler with rotation and timestamps
file_handler = logging.handlers.RotatingFileHandler(
self.log_file,
maxBytes=self.max_bytes,
backupCount=self.backup_count
)
file_formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
file_handler.setFormatter(file_formatter)

# Add handlers
self.logger.addHandler(console_handler)
self.logger.addHandler(file_handler)
self.logger.setLevel(logging.INFO)

@staticmethod
def get_instance():
Expand All @@ -37,10 +70,30 @@ def set_level(self, level):
}
self.level = level.upper()
self.logger.setLevel(level_map.get(level.upper(), logging.INFO))

# Update file handler level to match
for handler in self.logger.handlers:
if isinstance(handler, logging.handlers.RotatingFileHandler):
handler.setLevel(level_map.get(level.upper(), logging.INFO))

def get_level(self):
return self.level

def configure_from_args(self, log_file=None, max_bytes=None, backup_count=None):
"""Reconfigure logger with new parameters"""
if log_file:
self.log_file = log_file
if max_bytes:
self.max_bytes = max_bytes
if backup_count:
self.backup_count = backup_count

# Recreate logs directory if needed
os.makedirs(os.path.dirname(self.log_file), exist_ok=True)

# Re-setup handlers with new configuration
self._setup_handlers()

def debug(self, msg, *args, **kwargs):
self.logger.debug("\033[94m" + msg + "\033[0m", *args, **kwargs)

Expand Down
47 changes: 43 additions & 4 deletions testing/test_suites_extractor.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
import yaml
import os
from testing.logger import Logger
import jsonschema


class TestSuitesExtractor:
"""Singleton class to extract the test suites from the yaml file"""

_instance = None

# Schema definition for YAML validation
YAML_SCHEMA = {
"type": "object",
"required": ["project", "repo-url", "code-files"],
"properties": {
"project": {"type": "string"},
"repo-url": {"type": "string"},
"code-files": {
"type": "array",
"items": {
"type": "object",
"required": ["path", "test-cases"],
"properties": {
"path": {"type": "string"},
"test-cases": {
"type": "array",
"items": {
"type": "object",
"required": ["name", "function-call", "expected-pattern"],
"properties": {
"name": {"type": "string"},
"function-call": {"type": "string"},
"expected-pattern": {"type": "string"}
}
}
}
}
}
}
}
}

def __init__(self, file_name):
if TestSuitesExtractor._instance is not None:
raise Exception("This class is a singleton!")
Expand All @@ -26,21 +60,26 @@ def extract_test_suites(self):
"""Load the data from the yaml file and extract the test suites"""
try:
with open(self.file_name, "r", encoding="utf-8") as f:
data = yaml.load(f, Loader=yaml.FullLoader)
data = yaml.safe_load(f)
except FileNotFoundError:
self.logger.error(f"Error: file ({self.file_name}) does not exist!")
raise FileNotFoundError(f"Error: file ({self.file_name}) does not exist!")
except yaml.YAMLError as e:
self.logger.error(f"Error: parsing yaml file, {e}")
raise yaml.YAMLError(f"Error: parsing yaml file, {e}")

# Validate YAML structure against schema
try:
jsonschema.validate(data, self.YAML_SCHEMA)
except jsonschema.ValidationError as e:
self.logger.error(f"Error: YAML validation failed - {e.message}")
raise ValueError(f"Invalid YAML structure: {e.message}")

try:
project_name = data["project"]
repo_url = data["repo-url"]
code_files = data["code-files"]
project_path = "/".join(
code_files[0]["path"].split("/")[:-1]
) # take the path of the first file and get the parent directory
project_path = os.path.dirname(code_files[0]["path"])
test_suites = []
for code_file in code_files:
test_cases = [
Expand Down
Loading