Coverage for src/robotide/run/process.py: 90%

78 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-06 10:40 +0100

1# Copyright 2008-2015 Nokia Networks 

2# Copyright 2016- Robot Framework Foundation 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); 

5# you may not use this file except in compliance with the License. 

6# You may obtain a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, 

12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

13# See the License for the specific language governing permissions and 

14# limitations under the License. 

15 

16import os 

17import subprocess 

18import tempfile 

19import time 

20from ..publish import RideRunnerStarted, RideRunnerStopped 

21 

22 

23class Process(object): 

24 

25 def __init__(self, command): 

26 self._command = self._parse_command(command) 1gdebca

27 self._process = None 1gdebca

28 self._error = None 1gdebca

29 self._out_file = None 1gdebca

30 self._out_path = None 1gdebca

31 self._out_fd = None 1gdebca

32 self._fuse = False 1gdebca

33 self._pid =None 1gdebca

34 

35 @property 

36 def pid(self): 

37 return self._pid 1ebca

38 

39 @staticmethod 

40 def _parse_command(command): 

41 if isinstance(command, str): 41 ↛ 43line 41 didn't jump to line 43 because the condition on line 41 was always true1gdebca

42 return [val.replace('<SPACE>', ' ') for val in command.split()] 1gdebca

43 return command 

44 

45 def start(self): 

46 self._out_fd, self._out_path = tempfile.mkstemp(prefix='rfproc_', suffix='.txt', text=True) 1debca

47 self._out_file = open(self._out_path, 'w+b') 1debca

48 if not self._command: 48 ↛ 49line 48 didn't jump to line 49 because the condition on line 48 was never true1debca

49 self._error = 'The command is missing from this run configuration.' 

50 return 

51 try: 1debca

52 self._process = subprocess.Popen(self._command, stdout=self._out_fd, stderr=subprocess.STDOUT) 1debca

53 self._pid = self._process.pid 1dbca

54 RideRunnerStarted(process=self._pid).publish() 1dbca

55 except OSError as err: 1e

56 self._error = str(err) 1e

57 

58 def is_finished(self): 

59 return self._error is not None or self._process.poll() is not None 1debca

60 

61 def stop(self): 

62 self._process.kill() 1a

63 self._close_outputs() 1a

64 RideRunnerStopped(process=self._pid).publish() 1a

65 

66 def wait(self): 

67 if self._process is not None: 1ebc

68 self._process.wait() 1bc

69 

70 def get_output(self, wait_until_finished=False): 

71 """Returns the output produced by the process. 

72 

73 If ``wait_until_finished`` is True, blocks until the process is 

74 finished and returns all output. Otherwise, the currently available 

75 output is returned immediately. 

76 

77 Currently available output depends on buffering and might not include 

78 everything that has been written by the process. 

79 """ 

80 if self._error: 1debca

81 self._close_outputs() 1e

82 return self._error 1e

83 if wait_until_finished: 1dbca

84 self._process.wait() 1d

85 try: 1dbca

86 output = self._out_file.read() 1dbca

87 except ValueError: 1a

88 output = b"\nRIDE: ValueError when reading output.\n\n" 1a

89 self._fuse = True 1a

90 self._close_outputs() 1a

91 return output 1a

92 if self.is_finished(): 92 ↛ 94line 92 didn't jump to line 94 because the condition on line 92 was always true1dbc

93 self._close_outputs() 1dbc

94 return output 1dbc

95 

96 def _close_outputs(self): 

97 self._out_file.close() 1debca

98 try: 1debca

99 if not self._fuse: 1debca

100 os.close(self._out_fd) 1debca

101 self._remove_tempfile() 1debca

102 except ValueError: 

103 self._fuse = True 

104 return 

105 

106 def _remove_tempfile(self, attempts=5): 

107 try: 1debca

108 os.remove(self._out_path) 1debca

109 except OSError: 1a

110 if not attempts: 1a

111 self._fuse = True 1a

112 return 1a

113 time.sleep(1) 1a

114 self._remove_tempfile(attempts - 1) 1a