From ca4f4cc0bd1726bc753ad1ad2f32c752f65ccf50 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sun, 12 Jul 2026 22:15:24 +0100 Subject: [PATCH 1/6] Avoid test failures caused by the port not being free. --- README.md | 5 ++--- tests/bash_tests/testcases.py | 10 ++++------ tests/bash_tests/utils.py | 23 +++++++++++++++-------- tests/suite.conf | 2 -- tests/system_tests.py | 1 - 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 116f63ff44..49e46d2e39 100644 --- a/README.md +++ b/README.md @@ -982,7 +982,6 @@ Exiv2 optionally uses several different environment variables when building or t | Variable | Default | Platforms | Purpose | |:-- |:-- |:-- |:-- | | EXIV2_BINDIR | **\/build/bin** | All Platforms | Path of built binaries (e.g., exiv2.exe) | -| EXIV2_PORT | **12762**
**12671**
**12760** | Cygwin
MinGW/msys2
Other Platforms | Test TCP/IP Port | | EXIV2_HTTP | **http://localhost** | All Platforms | Test http server | | EXIV2_ECHO | _**not set**_ | All Platforms | For debugging bashTests | | VALGRIND | _**not set**_ | All Platforms | For debugging bashTests | @@ -1071,9 +1070,9 @@ c:\...\exiv2>ctest --test-dir build -C Release If you wish to use an environment variables, use set: ``` -set EXIV2_PORT=54321 +set EXIV2_HTTP="http://127.0.0.1" ctest --test-dir build -C Release --verbose -R bash -set EXIV2_PORT= +set EXIV2_HTTP= ``` [TOC](#TOC) diff --git a/tests/bash_tests/testcases.py b/tests/bash_tests/testcases.py index c366017d5e..91a9231bff 100644 --- a/tests/bash_tests/testcases.py +++ b/tests/bash_tests/testcases.py @@ -578,17 +578,15 @@ def sniff(*files): return ' '.join(result) exiv2_http = BT.Config.exiv2_http - exiv2_port = BT.Config.exiv2_port - if not exiv2_http or not exiv2_port: - print('Skipped http test. Because of invalid environment variables: EXIV2_HTTP={} EXIV2_PORT={}'.format( - exiv2_http, exiv2_port)) + if not exiv2_http: + print('Skipped http test. Because of invalid environment variables: EXIV2_HTTP={}'.format( + exiv2_http)) return - server_url = f'{exiv2_http}:{exiv2_port}' server = BT.HttpServer(bind=exiv2_http.lstrip('http://'), - port=exiv2_port, # It can be of type int or str work_dir=BT.Config.data_dir) try: server.start() + server_url = f'{exiv2_http}:{server.port}' out = BT.Output() for img in ['table.jpg', 'Reagan.tiff', 'exiv2-bug922a.jpg']: files = ['s0', 's1', 's2', f'{server_url}/{img}'] diff --git a/tests/bash_tests/utils.py b/tests/bash_tests/utils.py index e2e8c793df..f5fa651d14 100644 --- a/tests/bash_tests/utils.py +++ b/tests/bash_tests/utils.py @@ -48,10 +48,7 @@ class Config: tmp_dir = os.path.join(exiv2_dir, 'test/tmp') system_name = platform.system() or 'Unknown' # It could be Windows, Linux, etc. exiv2_http = 'http://127.0.0.1' - exiv2_port = '12760' valgrind = '' - if 'EXIV2_PORT' in os.environ: - exiv2_port = os.environ['EXIV2_PORT'] if 'EXIV2_HTTP' in os.environ: exiv2_http = os.environ['EXIV2_HTTP'] if 'VALGRIND' in os.environ: @@ -342,23 +339,33 @@ def error(self, msg): class HttpServer: - def __init__(self, bind='127.0.0.1', port=80, work_dir='.'): + def __init__(self, bind='127.0.0.1', work_dir='.'): self.bind = bind - self.port = int(port) + self.port = -1 self.work_dir = work_dir - def _start(self): + def _start(self, shared_port): """ Equivalent to executing `python3 -m http.server` """ os.chdir(self.work_dir) - server.test(HandlerClass=server.SimpleHTTPRequestHandler, bind=self.bind, port=self.port) + # Loop looking for a free port + for port in range(0x1000,0x10000): + # Share the port number with the parent process + shared_port.value = port + try: + server.test(HandlerClass=server.SimpleHTTPRequestHandler, bind=self.bind, port=port) + except: + continue log.error('The HTTP server exits without calling stop()') print(log.to_str()) def start(self): log.info('Starting HTTP server ...') - self.proc = multiprocessing.Process(target=self._start, name=str(self)) + shared_port = multiprocessing.Value('i', -1) + self.proc = multiprocessing.Process(target=self._start, name=str(self), args=(shared_port,)) self.proc.start() time.sleep(2) + self.port = shared_port.value + log.info(f"HTTP server is running on port {self.port}") try: with request.urlopen(f'http://127.0.0.1:{self.port}', timeout=3) as f: if f.status != 200: diff --git a/tests/suite.conf b/tests/suite.conf index 249a97fa93..ab65449ceb 100644 --- a/tests/suite.conf +++ b/tests/suite.conf @@ -7,7 +7,6 @@ exiv2_path: EXIV2_BINDIR dyld_library_path: DYLD_LIBRARY_PATH ld_library_path: LD_LIBRARY_PATH exiv2_http: EXIV2_HTTP -exiv2_port: EXIV2_PORT exiv2_echo: EXIV2_ECHO verbose: VERBOSE valgrind: VALGRIND @@ -17,7 +16,6 @@ exiv2_path: ../build/bin dyld_library_path: ${ENV:exiv2_path}/../lib ld_library_path: ${ENV:exiv2_path}/../lib exiv2_http: http://127.0.0.1 -exiv2_port: 12760 exiv2_echo: verbose: valgrind: diff --git a/tests/system_tests.py b/tests/system_tests.py index a37044021d..2e1fac45b9 100644 --- a/tests/system_tests.py +++ b/tests/system_tests.py @@ -191,7 +191,6 @@ def configure_suite(config_file): BT.Config.data_dir = os.path.abspath(config['paths']['data_path']) BT.Config.tmp_dir = os.path.abspath(config['paths']['tmp_path']) BT.Config.exiv2_http = config['ENV']['exiv2_http'] - BT.Config.exiv2_port = config['ENV']['exiv2_port'] BT.Config.exiv2_echo = config['ENV']['exiv2_echo'] BT.Config.verbose = config['ENV']['verbose'] BT.Config.valgrind = config['ENV']['valgrind'] From e7b699a82f047655fad078a3170307c04538ad58 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sun, 12 Jul 2026 22:37:32 +0100 Subject: [PATCH 2/6] Loop a few times until the HTTP server is started. --- tests/bash_tests/utils.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/bash_tests/utils.py b/tests/bash_tests/utils.py index f5fa651d14..54de351594 100644 --- a/tests/bash_tests/utils.py +++ b/tests/bash_tests/utils.py @@ -363,16 +363,19 @@ def start(self): shared_port = multiprocessing.Value('i', -1) self.proc = multiprocessing.Process(target=self._start, name=str(self), args=(shared_port,)) self.proc.start() - time.sleep(2) - self.port = shared_port.value - log.info(f"HTTP server is running on port {self.port}") - try: - with request.urlopen(f'http://127.0.0.1:{self.port}', timeout=3) as f: - if f.status != 200: - raise RuntimeError() - except Exception as e: - raise RuntimeError(f'Failed to run the HTTP server: {e}') from e - log.info('The HTTP server started') + for i in range(0,5): + time.sleep(2) + self.port = shared_port.value + log.info(f"HTTP server is running on port {self.port}") + try: + with request.urlopen(f'http://127.0.0.1:{self.port}', timeout=3) as f: + if f.status != 200: + raise RuntimeError() + log.info('The HTTP server started') + return + except: + continue + raise RuntimeError('Failed to run the HTTP server') def stop(self): self.proc.terminate() From f59f5d4a7379e6368ef49259b3d98eec95c563e2 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sun, 12 Jul 2026 22:53:31 +0100 Subject: [PATCH 3/6] Signal when the http server has stopped running --- tests/bash_tests/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bash_tests/utils.py b/tests/bash_tests/utils.py index 54de351594..78eddd0bf1 100644 --- a/tests/bash_tests/utils.py +++ b/tests/bash_tests/utils.py @@ -355,6 +355,7 @@ def _start(self, shared_port): server.test(HandlerClass=server.SimpleHTTPRequestHandler, bind=self.bind, port=port) except: continue + shared_port.value = -1 log.error('The HTTP server exits without calling stop()') print(log.to_str()) From 8e6da1abdc5e7135869276b73729e356f37ef182 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Tue, 21 Jul 2026 23:00:45 +0100 Subject: [PATCH 4/6] Wait for up to 60 seconds. --- tests/bash_tests/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bash_tests/utils.py b/tests/bash_tests/utils.py index 78eddd0bf1..68051b5cb4 100644 --- a/tests/bash_tests/utils.py +++ b/tests/bash_tests/utils.py @@ -364,8 +364,8 @@ def start(self): shared_port = multiprocessing.Value('i', -1) self.proc = multiprocessing.Process(target=self._start, name=str(self), args=(shared_port,)) self.proc.start() - for i in range(0,5): - time.sleep(2) + for i in range(0,60): + time.sleep(1) self.port = shared_port.value log.info(f"HTTP server is running on port {self.port}") try: From 8cb35880d685ebb549acb087e445a94d7463c748 Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 22 Jul 2026 14:38:18 +0100 Subject: [PATCH 5/6] Improve pause logic --- tests/bash_tests/utils.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/bash_tests/utils.py b/tests/bash_tests/utils.py index 68051b5cb4..a0e513c7c0 100644 --- a/tests/bash_tests/utils.py +++ b/tests/bash_tests/utils.py @@ -364,17 +364,22 @@ def start(self): shared_port = multiprocessing.Value('i', -1) self.proc = multiprocessing.Process(target=self._start, name=str(self), args=(shared_port,)) self.proc.start() - for i in range(0,60): - time.sleep(1) + start_time = time.time() + wait_time = 3 + for i in range(0,30): self.port = shared_port.value log.info(f"HTTP server is running on port {self.port}") try: - with request.urlopen(f'http://127.0.0.1:{self.port}', timeout=3) as f: + with request.urlopen(f'http://{self.bind}:{self.port}', timeout=wait_time) as f: if f.status != 200: raise RuntimeError() log.info('The HTTP server started') return except: + # Pause if request.urlopen returned too quickly + delta = start_time + i * wait_time - time.time() + if delta > 0: + time.sleep(delta) continue raise RuntimeError('Failed to run the HTTP server') From 96ef48eddbebd3a5027abe4cbc1898d22c52f66f Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Wed, 22 Jul 2026 16:22:49 +0100 Subject: [PATCH 6/6] copilot feedback --- README.md | 2 +- tests/bash_tests/testcases.py | 2 +- tests/bash_tests/utils.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 49e46d2e39..0725c4a35b 100644 --- a/README.md +++ b/README.md @@ -988,7 +988,7 @@ Exiv2 optionally uses several different environment variables when building or t | VERBOSE | _**not set**_ | Makefile platforms | Instructs make to report its actions | | PATH
DYLD\_LIBRARY\_PATH
LD\_LIBRARY\_PATH | $EXIV2\_BINDIR/../lib | Windows
macOS
Other platforms | Path of dynamic libraries | -The Variable EXIV2\_PORT or EXIV2\_HTTP can be set to None to skip http tests. The http server is started with the command `python3 -m http.server $port`. On Windows, you will need to run this manually _**once**_ to authorise the firewall to permit python to use the port. +The Variable EXIV2\_HTTP can be set to None to skip http tests. The http server is started with the command `python3 -m http.server $port`. On Windows, you will need to run this manually _**once**_ to authorise the firewall to permit python to use the port. [TOC](#TOC)
diff --git a/tests/bash_tests/testcases.py b/tests/bash_tests/testcases.py index 91a9231bff..1b7de520f4 100644 --- a/tests/bash_tests/testcases.py +++ b/tests/bash_tests/testcases.py @@ -582,7 +582,7 @@ def sniff(*files): print('Skipped http test. Because of invalid environment variables: EXIV2_HTTP={}'.format( exiv2_http)) return - server = BT.HttpServer(bind=exiv2_http.lstrip('http://'), + server = BT.HttpServer(bind=exiv2_http.removeprefix('http://'), work_dir=BT.Config.data_dir) try: server.start() diff --git a/tests/bash_tests/utils.py b/tests/bash_tests/utils.py index a0e513c7c0..0e34cfb9a8 100644 --- a/tests/bash_tests/utils.py +++ b/tests/bash_tests/utils.py @@ -377,7 +377,7 @@ def start(self): return except: # Pause if request.urlopen returned too quickly - delta = start_time + i * wait_time - time.time() + delta = start_time + (i+1) * wait_time - time.time() if delta > 0: time.sleep(delta) continue