diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 38cb8f41..a379bbc9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,10 +40,6 @@ add_library( exiv2lib_int OBJECT unused.h ) -if (COMPILER_IS_GCC OR COMPILER_IS_CLANG) - set_source_files_properties(http.cpp PROPERTIES COMPILE_FLAGS -fno-sanitize=address,undefined) -endif() - add_library( exiv2lib ../include/exiv2/config.h ../include/exiv2/exiv2.hpp diff --git a/test/Makefile b/test/Makefile index 64997e65..8bf481eb 100644 --- a/test/Makefile +++ b/test/Makefile @@ -83,6 +83,7 @@ TESTS = addmoddel.sh \ iotest.sh \ iptctest.sh \ iso65k-test.sh \ + nls-test.sh \ modify-test.sh \ path-test.sh \ png-test.sh \ @@ -112,6 +113,7 @@ exiv2-test \ imagetest \ iotest \ iptctest \ +nls-test \ preview-test \ tiff-test \ write-test \ diff --git a/test/data/nls-test.out b/test/data/nls-test.out new file mode 100644 index 00000000..9312e3fa --- /dev/null +++ b/test/data/nls-test.out @@ -0,0 +1,10 @@ +exiv2: Une action doit être spécifié +exiv2: Au moins un fichier est nécessaire +Utilisation : exiv2 [ options ] [ action ] fichier ... + +Manipulation des métadonnées EXIF issues des images. +exiv2: Se debe especificar una acción +exiv2: Se requiere un archivo al menos +Uso: exiv2 [ opciones ] [ acción ] archivo ... + +manipular los metadatos Exif de las imágenes. diff --git a/test/nls-test.sh b/test/nls-test.sh new file mode 100755 index 00000000..7948d30c --- /dev/null +++ b/test/nls-test.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Test driver for exiv2.exe nls support + +source ./functions.source + +( cd "$testdir" + + nls=$(runTest exiv2 -vVg nls|tail -1) + platform=$(${bin}exiv2${exe} -vVg platform|tail -1) + if [ "$nls" != "enable_nls=1" ]; then + echo "exiv2 not bulid with nls" + exit 0 + fi + if [ "$platform" == "platform=windows" ]; then + echo "nls_test cannot be run msvc builds" >2 + exit 0 + fi + if [ "$platform" == "platform=linux" ]; then + LANG=LANGUAGE + else + LANG=LANG + fi + ## + # if necessary ditto /usr/local/share/locale -> build/share/locale + share=${bin}../share + if [ ! -e $share ]; then + mkdir -p $share + fi + usr=/usr/local/share/locale + if [ -e "$usr" -a -e "$share" ]; then + cp -r "$usr" "$share" + else + echo "localisation files are not installed in $usr" + exit 0 + fi + ## + # test a couple of languages + for l in fr_FR es_ES; do ( + export LC_ALL=$l + export $LANG=$l + runTest exiv2 + ) done + +) 3>&1 > $results 2>&1 + +reportTest + +# That's all Folks! +## diff --git a/tests/bash_tests/testcases.py b/tests/bash_tests/testcases.py index 72df9421..a95e5d65 100644 --- a/tests/bash_tests/testcases.py +++ b/tests/bash_tests/testcases.py @@ -861,6 +861,40 @@ set Exif.Photo.DateTimeDigitized 2020:05:26 07:31:42 BT.reportTest('modify-test', out) + def nls_test(self): + # Test driver for exiv2.exe nls support + nls = BT.Executer('exiv2 -vVg nls').stdout.split('\n')[1] + platform = BT.Executer('exiv2 -vVg platform').stdout.split('\n')[1] + + if nls != 'enable_nls=1': + print('Skipped. Because exiv2 is not built with nls.') + return + + if platform == 'platform=windows': + print('Skipped. Because nls_test cannot be run msvc builds.') + return + + if platform == 'platform=linux': + LANG = 'LANGUAGE' + else: + LANG = 'LANG' + + share_dir = os.path.normpath(os.path.join(BT.Config.bin_dir, '..', 'share2')) + os.makedirs(share_dir, exist_ok=True) + + locale_dir = '/usr/local/share/locale' + if os.path.isdir(locale_dir) and os.path.isdir(share_dir): + BT.cp(locale_dir, share_dir) + else: + print('Skipped. Because localisation files are not installed in {}.'.format(locale_dir)) + + # The above part is checking the environment, and the following part is executing the actual test + out = BT.Output() + for language in ['fr_FR', 'es_ES']: + out += BT.Executer('exiv2', extra_env={'LC_ALL': language, LANG: language}, assert_returncode=[1]) + BT.reportTest('nls-test', out) + + def path_test(self): # Mini test-driver for path utility functions BT.copyTestFile('path-test.txt') @@ -956,7 +990,7 @@ set Exif.Photo.DateTimeDigitized 2020:05:26 07:31:42 e = BT.Executer('exiv2 -pp {filename}', vars(), assert_returncode=None, redirect_stderr_to_stdout=False) out += e.stdout out += 'Exit code: {}'.format(e.returncode) - BT.rm(*BT.find(image + '-preview*')) + BT.rm(*BT.find(pattern=image + '-preview*')) out += '\nCommand: exiv2 -f -ep ' + filename e = BT.Executer('exiv2 -f -ep {filename}', vars(), assert_returncode=None, redirect_stderr_to_stdout=False) @@ -966,7 +1000,7 @@ set Exif.Photo.DateTimeDigitized 2020:05:26 07:31:42 # Check the difference e = BT.Executer('exiv2 -pp {filename}', vars(), assert_returncode=None, redirect_stderr_to_stdout=False) preview_num = e.stdout[:e.stdout.find(':')].lstrip('Preview ') - for test_file in BT.find('{image}-preview{preview_num}.*'.format(**vars())): + for test_file in BT.find(pattern='{image}-preview{preview_num}.*'.format(**vars())): reference_file = os.path.join(preview_dir, test_file) if BT.diffCheck(reference_file, test_file, in_bytes=True): pass_count += 1 @@ -987,8 +1021,7 @@ set Exif.Photo.DateTimeDigitized 2020:05:26 07:31:42 try: import lxml except ModuleNotFoundError: - print('ignored') - print('Missing module lxml, please install: `pip install lxml`') + print('Skipped. Because it misses module lxml. Please install: `pip install lxml`') return out = BT.Output() diff --git a/tests/bash_tests/utils.py b/tests/bash_tests/utils.py index 73ecae3c..a7e319d9 100644 --- a/tests/bash_tests/utils.py +++ b/tests/bash_tests/utils.py @@ -44,36 +44,17 @@ Part 2: Here are some common functions that are poorly coupled with test cases. """ -def cp(src, dest): - """ It is used to copy one file, cannot handle directories """ - shutil.copy(src, dest) - -def mv(src, dest): - """ It is used to move one file, cannot handle directories """ - shutil.move(src, dest) - - -def rm(*files): - """ It is used to remove files, cannot handle directories """ - for i in files: - try: - os.remove(i) - except FileNotFoundError: - continue - - -def find(pattern=None, re_pattern=None, directory='.', depth=-1, onerror=print) -> list: +def find(directory='.', pattern=None, re_pattern=None, depth=-1, onerror=print) -> list: """ - Find files that match the pattern in the specified directory and return their paths. + Find files and directories that match the pattern in the specified directory and return their paths. + Work in recursive mode. If there are thousands of files, the runtime may be several seconds. + - `directory` : Find files in this directory and its subdirectories - `pattern` : Filter filename based on shell-style wildcards. - `re_pattern` : Filter filename based on regular expressions. - - `directory` : Find files in this directory and its subdirectories - `depth` : Depth of subdirectories. If its value is negative, the depth is infinite. - `onerror` : A callable parameter. it will be called if an exception occurs. - Work in recursive mode. If there are thousands of files, the runtime may be several seconds. - Sample: >>> find(pattern='*.py') >>> find(re_pattern='.*.py') @@ -85,25 +66,72 @@ def find(pattern=None, re_pattern=None, directory='.', depth=-1, onerror=print) file_list = os.listdir(directory) except PermissionError as e: # Sometimes it does not have access to the directory onerror("PermissionError: {}".format(e)) - return -1 + return [] + + def match(name, pattern=None, re_pattern=None): + if pattern and not fnmatch.fnmatch(name, pattern): + return False + if re_pattern and not re.findall(re_pattern, name): + return False + return True path_list = [] - for filename in file_list: - path = os.path.join(directory, filename) - if depth != 0 and os.path.isdir(path): - sub_list = find(path, depth-1, pattern, re_pattern, onerror) - if sub_list != -1: - path_list.extend(sub_list) - continue - if pattern and not fnmatch.fnmatch(filename, pattern): - continue - if re_pattern and not re.findall(re_pattern, filename): - continue - path_list.append(path) + if match(os.path.basename(directory), pattern, re_pattern): + path_list.append(directory) + if depth != 0: + for filename in file_list: + path = os.path.join(directory, filename) + if os.path.isdir(path): + path_list.extend(find(path, pattern, re_pattern, depth-1, onerror)) + continue + if match(filename, pattern, re_pattern): + path_list.append(path) return path_list +def cp(src, dst): + """ Copy one or more files or directories. It simulates `cp -rf src dst`. """ + if os.path.isfile(src): + shutil.copy(src, dst) + elif os.path.isdir(src): + if os.path.isdir(dst): + dst_dir = os.path.join(dst, os.path.basename(src)) + else: + dst_dir = dst + for src_path in find(src): + relpath = os.path.relpath(src_path, src) + dst_path = os.path.join(dst_dir, relpath) + if os.path.isdir(src_path): + os.makedirs(dst_path, exist_ok=True) + else: + shutil.copy(src_path, dst_path) + else: + raise ValueError('src is not a valid path to a file or directory.') + + +def rm(*paths): + """ Remove one or more files or directories. It simulates `rm -rf paths`. """ + for path in paths: + if os.path.isfile(path): + os.remove(path) + elif os.path.isdir(path): + for sub_path in find(path, depth=1)[1:]: + if os.path.isdir(sub_path): + rm(sub_path) + else: + os.remove(sub_path) + os.rmdir(path) # Remove the directory only when it is empty + else: + continue + + +def mv(src, dst): + """ Move one or more files or directories. """ + cp(src, dst) + rm(src) + + def cat(*files, encoding=None, return_bytes=False): if return_bytes: result = b'' @@ -319,12 +347,12 @@ Part 3: Here are some functions that are highly coupled to test cases. """ -def copyTestFile(src, dest=''): +def copyTestFile(src, dst=''): """ Copy one test file from data_dir to tmp_dir """ - if not dest: - dest = src + if not dst: + dst = src shutil.copy(os.path.join(Config.data_dir, src), - os.path.join(Config.tmp_dir, dest)) + os.path.join(Config.tmp_dir, dst)) def diffCheck(file1, file2, in_bytes=False, encoding=None): @@ -382,6 +410,7 @@ class Executer: def __init__(self, cmd: str, vars_dict=dict(), cwd=None, + extra_env=dict(), encoding=None, stdin: (str, bytes) = None, redirect_stderr_to_stdout=True, @@ -390,6 +419,12 @@ class Executer: decode_output=True): self.cmd = cmd.format(**vars_dict) self.cwd = cwd or Config.tmp_dir + + # set environment variables + self.env = os.environ.copy() + self.env.update({'TZ': 'GMT-8'}) + self.env.update(extra_env) + self.encoding = encoding or Config.encoding self.stdin = stdin # self.stdout = None @@ -410,7 +445,7 @@ class Executer: else: self.args = shlex.split(args, posix=os.name == 'posix') - # check stdin + # Check stdin if self.stdin: if not isinstance(stdin, bytes): self.stdin = str(stdin).encode(self.encoding) @@ -426,9 +461,9 @@ class Executer: # Execute the command in subprocess try: - my_env = os.environ.copy() - my_env['TZ'] = 'GMT-8' - with subprocess.Popen(self.args,env=my_env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr, cwd=self.cwd) as self.subprocess: + with subprocess.Popen(self.args, cwd=self.cwd, env=self.env, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=stderr) as self.subprocess: try: output = self.subprocess.communicate(self.stdin, timeout=10) # Assign (stdout, stderr) to output except subprocess.TimeoutExpired: @@ -447,7 +482,7 @@ class Executer: output = [i.decode(self.encoding) for i in output] self.stdout, self.stderr = [i or None for i in output] - # check return code + # Check return code self.returncode = self.subprocess.returncode if self.assert_returncode and self.returncode not in self.assert_returncode: log.error('Failed to execute: {}'.format(self.args))