File: test_exiftool_process.py

package info (click to toggle)
pyexiftool 0.5.6-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 356 kB
  • sloc: python: 1,406; makefile: 5
file content (121 lines) | stat: -rw-r--r-- 4,583 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# -*- coding: utf-8 -*-
"""
Test :: ExifTool base class - process-related tests
"""

# standard
import unittest
import warnings

# custom
import exiftool
from exiftool.exceptions import ExifToolNotRunning


# bool which is set to True when running on Windows
# used below to workaround a Windows buggy interaction with exiftool subprocess
from exiftool.constants import PLATFORM_WINDOWS


class TestExifToolProcess(unittest.TestCase):


	# ---------------------------------------------------------------------------------------------------------
	def setUp(self):
		self.et = exiftool.ExifTool(common_args=["-G", "-n", "-overwrite_original"])

	def tearDown(self):
		if hasattr(self, "et"):
			if self.et.running:
				self.et.terminate()
		if hasattr(self, "process"):
			if self.process.poll() is None:
				self.process.terminate()


	# ---------------------------------------------------------------------------------------------------------
	def test_termination_cm(self):
		# Test correct subprocess start and termination when using
		# self.et as a context manager
		self.assertFalse(self.et.running)
		self.assertRaises(ExifToolNotRunning, self.et.execute)
		with self.et:
			self.assertTrue(self.et.running)
			with warnings.catch_warnings(record=True) as w:
				self.et.run()
				self.assertEqual(len(w), 1)
				self.assertTrue(issubclass(w[0].category, UserWarning))
			self.process = self.et._process
			self.assertEqual(self.process.poll(), None)
		self.assertFalse(self.et.running)
		self.assertNotEqual(self.process.poll(), None)


	# ---------------------------------------------------------------------------------------------------------
	def test_termination_explicit(self):
		# Test correct subprocess start and termination when
		# explicitly using start() and terminate()
		self.et.run()
		self.process = self.et._process
		self.assertEqual(self.process.poll(), None)
		self.et.terminate()
		self.assertNotEqual(self.process.poll(), None)

		# terminate when not running
		with warnings.catch_warnings(record=True) as w:
			self.et.terminate()
			self.assertEqual(len(w), 1)
			self.assertTrue(issubclass(w[0].category, UserWarning))


	# ---------------------------------------------------------------------------------------------------------
	def test_process_died_running_status(self):
		""" Test correct .running status if process dies by itself """

		# There is a very weird bug triggered on WINDOWS only which I've described here: https://exiftool.org/forum/index.php?topic=12472.0
		# it happens specifically when you forcefully kill the process, but at least one command has run since launching, the exiftool wrapper on windows does not terminate the child process
		# it's a very strange interaction and causes a zombie process to remain, and python hangs
		#
		# either kill the tree with psutil, or do it this way...

		# WINDOWS WORKAROUND: take out the method that is called on load (probably not the way to do this well... you can take out this line and watch Python interpreter hang at .kill() below
		if PLATFORM_WINDOWS:
			self.et._parse_ver = lambda: None


		self.et.run()
		self.process = self.et._process
		self.assertTrue(self.et.running)

		# kill the process, out of ExifTool's control
		self.process.kill()
		# TODO freeze here on windows if there is a zombie process b/c killing immediate exiftool does not kill the spawned subprocess
		outs, errs = self.process.communicate()

		with warnings.catch_warnings(record=True) as w:
			self.assertFalse(self.et.running)
			self.assertEqual(len(w), 1)
			self.assertTrue(issubclass(w[0].category, UserWarning))

		# after removing that function, delete the object so it gets recreated cleanly
		del self.et


	# ---------------------------------------------------------------------------------------------------------
	def test_termination_implicit(self):
		# Test implicit process termination on garbage collection

		# WINDOWS WORKAROUND: take out the method that is called on load (see test_process_died_running_status())
		if PLATFORM_WINDOWS:
			self.et._parse_ver = lambda: None

		self.et.run()
		self.process = self.et._process
		# TODO freze here on windows for same reason as in test_process_died_running_status() as a zombie process remains
		del self.et
		self.assertNotEqual(self.process.poll(), None)


# ---------------------------------------------------------------------------------------------------------
if __name__ == '__main__':
	unittest.main()