add unit test

This commit is contained in:
lukas 2022-03-17 21:24:56 +01:00
parent 8f379f3b69
commit 3e2cf3ee0f
2 changed files with 228 additions and 24 deletions

93
ex2.py
View File

@ -1,7 +1,14 @@
"""
Author: Lukas Heiligenbrunner
Matr.Nr.: K12104785
Exercise 2
"""
import os.path import os.path
from enum import Enum from enum import Enum
from glob import glob from glob import glob
from typing import TextIO from typing import TextIO
import hashlib
import PIL import PIL
from PIL import Image, ImageStat from PIL import Image, ImageStat
@ -14,7 +21,7 @@ def mkdirIfNotExists(path: str):
def validSuffix(file: str) -> bool: def validSuffix(file: str) -> bool:
return file.endswith((".jpg", ".jpeg", ".JPG")) return file.endswith((".jpg", ".jpeg", ".JPG", ".JPEG"))
def validFileSize(file: str) -> bool: def validFileSize(file: str) -> bool:
@ -53,7 +60,8 @@ class FileLogger(metaclass=Singleton):
self.file.close() self.file.close()
def log(self, filename: str, errorcode: ErrorCodes): def log(self, filename: str, errorcode: ErrorCodes):
self.file.write(f"{filename},{errorcode.value}\n") # enforce a CRLF line ending with \r\n !!
self.file.write(f"{os.path.basename(filename)};{errorcode.value}\r\n")
def openImage(path: str) -> PIL.Image.Image or None: def openImage(path: str) -> PIL.Image.Image or None:
@ -89,54 +97,79 @@ def validVariance(img: PIL.Image.Image) -> bool:
return min(variances) > .0 return min(variances) > .0
def processFile(f: str): class FileCopy(metaclass=Singleton):
format: str
out_dir: str
hashmap: [str]
idx: int
def init(self, out_dir: str, format: str):
self.format = format
self.out_dir = out_dir
self.idx = 1
self.hashmap = []
def copyFile(self, img: PIL.Image.Image) -> bool:
hexhash = hashlib.md5(img.__array__()).hexdigest()
if hexhash in self.hashmap:
return False
# element already copied
else:
self.hashmap.append(hexhash)
# apply format string
filename = f"%{self.format}" % self.idx
self.idx += 1
# copy file
im = PIL.Image.fromarray(img.__array__())
im.save(os.path.join(self.out_dir, f"{filename}.jpg"), format="jpeg")
im.close()
return True
def processFile(f: str) -> bool:
# check suffix # check suffix
if not validSuffix(f): if not validSuffix(f):
log(f, ErrorCodes.Extension) log(f, ErrorCodes.Extension)
FileLogger().log(f, ErrorCodes.Extension) FileLogger().log(f, ErrorCodes.Extension)
return return False
# check file size # check file size
if not validFileSize(f): if not validFileSize(f):
log(f, ErrorCodes.FileSize) log(f, ErrorCodes.FileSize)
FileLogger().log(f, ErrorCodes.FileSize) FileLogger().log(f, ErrorCodes.FileSize)
return return False
img = openImage(f) img = openImage(f)
if img is None: if img is None:
log(f, ErrorCodes.InvalidImage) log(f, ErrorCodes.InvalidImage)
FileLogger().log(f, ErrorCodes.InvalidImage) FileLogger().log(f, ErrorCodes.InvalidImage)
return return False
if not validImageShape(img): if not validImageShape(img):
log(f, ErrorCodes.ImageShape) log(f, ErrorCodes.ImageShape)
FileLogger().log(f, ErrorCodes.ImageShape) FileLogger().log(f, ErrorCodes.ImageShape)
return return False
if not validVariance(img): if not validVariance(img):
log(f, ErrorCodes.VarianceLZero) log(f, ErrorCodes.VarianceLZero)
FileLogger().log(f, ErrorCodes.VarianceLZero) FileLogger().log(f, ErrorCodes.VarianceLZero)
return return False
# lets copy the image data # lets copy the image data
# todo check if image has already be copied if not FileCopy().copyFile(img):
log(f, ErrorCodes.Duplicate)
FileLogger().log(f, ErrorCodes.Duplicate)
return False
# ok nice if we are here we have successfully copied the img (:
# close image # close image
img.close() img.close()
return True
def findFiles(path: str) -> [str]: def validate_images(input_dir: str, output_dir: str, log_file: str, formatter: str = "05d") -> int:
files = glob(path + '/**/*', recursive=True)
print(files)
# sort filenames
files.sort()
for f in files:
processFile(f)
def validate_images(input_dir: str, output_dir: str, log_file: str, formatter: str or None = None):
# check if out_put dir exists # check if out_put dir exists
mkdirIfNotExists(output_dir) mkdirIfNotExists(output_dir)
@ -146,12 +179,24 @@ def validate_images(input_dir: str, output_dir: str, log_file: str, formatter: s
# init file logger # init file logger
FileLogger().init(log_file) FileLogger().init(log_file)
FileCopy().init(output_dir, formatter)
# scan input dir for files # scan input dir for files
findFiles(input_dir) files = glob(input_dir + '/**/*.*', recursive=True)
pass
# sort filenames
files.sort()
succcounter = 0
for f in files:
if processFile(f):
succcounter += 1
FileLogger().close()
return succcounter
# Press the green button in the gutter to run the script. # Press the green button in the gutter to run the script.
if __name__ == '__main__': if __name__ == '__main__':
validate_images("./input", "./output", "logs/1/log.txt") print(validate_images("unittest/unittest_input_0", "unittest/outputs/unittest_input_0",
"unittest/outputs/unittest_input_0.log", "06d"))

159
ex2_unittest.py Normal file
View File

@ -0,0 +1,159 @@
"""
Author -- Michael Widrich, Andreas Schörgenhumer
Contact -- schoergenhumer@ml.jku.at
Date -- 04.03.2022
###############################################################################
The following copyright statement applies to all code within this file.
Copyright statement:
This material, no matter whether in printed or electronic form,
may be used for personal and non-commercial educational use only.
Any reproduction of this manuscript, no matter whether as a whole or in parts,
no matter whether in printed or in electronic form, requires explicit prior
acceptance of the authors.
###############################################################################
Images taken from: https://pixabay.com/
"""
import hashlib
import os
import shutil
import sys
from glob import glob
import dill as pkl
def print_outs(outs, line_token="-"):
print(line_token * 40)
print(outs, end="" if isinstance(outs, str) and outs.endswith("\n") else "\n")
print(line_token * 40)
ex_file = "ex2.py"
full_points = 15
points = full_points
python = sys.executable
solutions_dir = os.path.join("unittest", "solutions")
outputs_dir = os.path.join("unittest", "outputs")
# Remove previous outputs folder
shutil.rmtree(outputs_dir, ignore_errors=True)
inputs = sorted(glob(os.path.join("unittest", "unittest_input_*"), recursive=True))
if not len(inputs):
raise FileNotFoundError("Could not find unittest_input_* files")
with open(os.path.join(solutions_dir, "counts.pkl"), "rb") as f:
sol_counts = pkl.load(f)
for test_i, input_folder in enumerate(inputs):
comment = ""
fcall = ""
with open(os.devnull, "w") as null:
# sys.stdout = null
try:
from ex2 import validate_images
proper_import = True
except Exception as e:
outs = ""
errs = e
points -= full_points / len(inputs)
proper_import = False
finally:
sys.stdout.flush()
sys.stdout = sys.__stdout__
if proper_import:
with open(os.devnull, "w") as null:
# sys.stdout = null
try:
input_basename = os.path.basename(input_folder)
output_dir = os.path.join(outputs_dir, input_basename)
logfilepath = output_dir + ".log"
formatter = "06d"
counts = validate_images(input_dir=input_folder, output_dir=output_dir, log_file=logfilepath,
formatter=formatter)
fcall = f'validate_images(\n\tinput_dir="{input_folder}",\n\toutput_dir="{output_dir}",\n\tlog_file="{logfilepath}",\n\tformatter="{formatter}"\n)'
errs = ""
try:
with open(os.path.join(outputs_dir, f"{input_basename}.log"), "r") as lfh:
logfile = lfh.read()
except FileNotFoundError:
# two cases:
# 1) no invalid files and thus no log file -> ok -> equal to empty tlogfile
# 2) invalid files but no log file -> not ok -> will fail the comparison with tlogfile (below)
logfile = ""
with open(os.path.join(solutions_dir, f"{input_basename}.log"), "r") as lfh:
# must replace the separator that was used when creating the solution files
tlogfile = lfh.read().replace("\\", os.path.sep)
files = sorted(glob(os.path.join(outputs_dir, input_basename, "**", "*"), recursive=True))
hashing_function = hashlib.sha256()
for file in files:
with open(file, "rb") as fh:
hashing_function.update(fh.read())
hash = hashing_function.digest()
hashing_function = hashlib.sha256()
tfiles = sorted(glob(os.path.join(solutions_dir, input_basename, "**", "*"), recursive=True))
for file in tfiles:
with open(file, "rb") as fh:
hashing_function.update(fh.read())
thash = hashing_function.digest()
tcounts = sol_counts[input_basename]
if not counts == tcounts:
points -= full_points / len(inputs)
comment = f"Function should return {tcounts} but returned {counts}"
elif not [f.split(os.path.sep)[-2:] for f in files] == [f.split(os.path.sep)[-2:] for f in tfiles]:
points -= full_points / len(inputs)
comment = f"Contents of output directory do not match (see directory 'solutions')"
elif not hash == thash:
points -= full_points / len(inputs)
comment = f"Hash value of the files in the output directory do not match (see directory 'solutions')"
elif not logfile == tlogfile:
points -= full_points / len(inputs)
comment = f"Contents of logfiles do not match (see directory 'solutions')"
except Exception as e:
outs = ""
errs = e
points -= full_points / len(inputs)
finally:
sys.stdout.flush()
sys.stdout = sys.__stdout__
print()
print_outs(f"Test {test_i}", line_token="#")
print("Function call:")
print_outs(fcall)
if errs:
print(f"Some unexpected errors occurred:")
print_outs(f"{type(errs).__name__}: {errs}")
else:
print("Notes:")
print_outs("No issues found" if comment == "" else comment)
# due to floating point calculations it could happen that we get -0 here
if points < 0:
assert abs(points) < 1e-7, f"points were {points} < 0: error when subtracting points?"
points = abs(points)
print(f"Current points: {points:.2f}")
print(f"\nEstimated points upon submission: {points:.2f} (out of {full_points:.2f})")
if points < full_points:
print(f"Check the folder '{outputs_dir}' to see where your errors are")
else:
shutil.rmtree(os.path.join(outputs_dir))
print(f"This is only an estimate, see 'Instructions for submitting homework' in Moodle "
f"for common mistakes that can still lead to 0 points.")