Coverage for tests / unit / cli / test_command.py: 100%

74 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-18 02:51 -0700

1from __future__ import annotations 

2 

3import os 

4import subprocess 

5 

6import pytest 

7 

8from muutils.cli.command import Command 

9 

10 

11def test_Command_init(): 

12 """Test Command initialization with list and string cmds.""" 

13 # Valid: list cmd with shell=False (default) 

14 cmd1 = Command(cmd=["echo", "hello"]) 

15 assert cmd1.cmd == ["echo", "hello"] 

16 assert cmd1.shell is False 

17 

18 # Valid: string cmd with shell=True 

19 cmd2 = Command(cmd="echo hello", shell=True) 

20 assert cmd2.cmd == "echo hello" 

21 assert cmd2.shell is True 

22 

23 # Invalid: string cmd with shell=False should raise ValueError 

24 with pytest.raises( 

25 ValueError, match="cmd must be List\\[str\\] when shell is False" 

26 ): 

27 Command(cmd="echo hello", shell=False) 

28 

29 # Valid: list cmd with shell=True is allowed (will be joined) 

30 cmd3 = Command(cmd=["echo", "hello"], shell=True) 

31 assert cmd3.cmd == ["echo", "hello"] 

32 assert cmd3.shell is True 

33 

34 

35def test_Command_properties(): 

36 """Test cmd_joined and cmd_for_subprocess properties in both shell modes.""" 

37 # Test with shell=False (list cmd) 

38 cmd_list = Command(cmd=["echo", "hello", "world"]) 

39 assert cmd_list.cmd_joined == "echo hello world" 

40 assert cmd_list.cmd_for_subprocess == ["echo", "hello", "world"] 

41 

42 # Test with shell=True and string cmd 

43 cmd_str = Command(cmd="echo hello world", shell=True) 

44 assert cmd_str.cmd_joined == "echo hello world" 

45 assert cmd_str.cmd_for_subprocess == "echo hello world" 

46 

47 # Test with shell=True and list cmd (should be joined for subprocess) 

48 cmd_list_shell = Command(cmd=["echo", "hello", "world"], shell=True) 

49 assert cmd_list_shell.cmd_joined == "echo hello world" 

50 assert cmd_list_shell.cmd_for_subprocess == "echo hello world" 

51 

52 

53def test_Command_script_line(): 

54 """Test script_line with env vars formatting.""" 

55 # No env vars 

56 cmd1 = Command(cmd=["echo", "hello"]) 

57 assert cmd1.script_line() == "echo hello" 

58 

59 # With env vars 

60 cmd2 = Command(cmd=["echo", "hello"], env={"FOO": "bar", "BAZ": "qux"}) 

61 script = cmd2.script_line() 

62 # env vars can be in any order, so check both are present 

63 assert "FOO=bar" in script 

64 assert "BAZ=qux" in script 

65 assert "echo hello" in script 

66 # Verify format: env vars come before command 

67 assert script.endswith("echo hello") 

68 

69 # With shell=True 

70 cmd3 = Command(cmd="echo $FOO", shell=True, env={"FOO": "bar"}) 

71 assert cmd3.script_line() == "FOO=bar echo $FOO" 

72 

73 

74def test_Command_env_final(): 

75 """Test env_final with inherit_env=True and inherit_env=False.""" 

76 # Set a test environment variable 

77 os.environ["TEST_VAR_COMMAND"] = "original" 

78 

79 try: 

80 # inherit_env=True (default) should merge with os.environ 

81 cmd1 = Command(cmd=["echo", "test"], env={"FOO": "bar"}) 

82 env1 = cmd1.env_final 

83 assert env1["FOO"] == "bar" 

84 assert env1["TEST_VAR_COMMAND"] == "original" 

85 

86 # inherit_env=False should only include provided env 

87 cmd2 = Command(cmd=["echo", "test"], env={"FOO": "bar"}, inherit_env=False) 

88 env2 = cmd2.env_final 

89 assert env2["FOO"] == "bar" 

90 assert "TEST_VAR_COMMAND" not in env2 

91 

92 # Custom env should override inherited env 

93 os.environ["OVERRIDE_TEST"] = "old" 

94 cmd3 = Command(cmd=["echo", "test"], env={"OVERRIDE_TEST": "new"}) 

95 env3 = cmd3.env_final 

96 assert env3["OVERRIDE_TEST"] == "new" 

97 

98 finally: 

99 # Clean up test env vars 

100 os.environ.pop("TEST_VAR_COMMAND", None) 

101 os.environ.pop("OVERRIDE_TEST", None) 

102 

103 

104def test_Command_run(): 

105 """Test running a simple command and capturing output.""" 

106 # Simple successful command 

107 cmd = Command(cmd=["echo", "hello"]) 

108 result = cmd.run(capture_output=True, text=True) 

109 assert result.returncode == 0 

110 assert "hello" in result.stdout 

111 

112 # Command with env vars 

113 cmd2 = Command(cmd=["sh", "-c", "echo $TEST_VAR"], env={"TEST_VAR": "test_value"}) 

114 result2 = cmd2.run(capture_output=True, text=True) 

115 assert result2.returncode == 0 

116 assert "test_value" in result2.stdout 

117 

118 # Shell command 

119 cmd3 = Command(cmd="echo shell test", shell=True) 

120 result3 = cmd3.run(capture_output=True, text=True) 

121 assert result3.returncode == 0 

122 assert "shell test" in result3.stdout 

123 

124 # Test that CalledProcessError is properly raised and handled 

125 cmd4 = Command(cmd=["sh", "-c", "exit 1"]) 

126 result4 = cmd4.run(capture_output=True) 

127 assert result4.returncode == 1 # Should not raise by default 

128 

129 # When check=True is passed, it should raise CalledProcessError 

130 cmd5 = Command(cmd=["sh", "-c", "exit 1"]) 

131 with pytest.raises(subprocess.CalledProcessError): 

132 cmd5.run(check=True, capture_output=True)