1
1
# Author: Steven J. Bethard <steven.bethard@gmail.com>.
2
2
3
3
import inspect
4
+ import io
5
+ import operator
4
6
import os
5
7
import shutil
6
8
import stat
11
13
import argparse
12
14
import warnings
13
15
14
- from io import StringIO
15
-
16
16
from test .support import os_helper
17
17
from unittest import mock
18
- class StdIOBuffer (StringIO ):
19
- pass
18
+
19
+
20
+ class StdIOBuffer (io .TextIOWrapper ):
21
+ '''Replacement for writable io.StringIO that behaves more like real file
22
+
23
+ Unlike StringIO, provides a buffer attribute that holds the underlying
24
+ binary data, allowing it to replace sys.stdout/sys.stderr in more
25
+ contexts.
26
+ '''
27
+
28
+ def __init__ (self , initial_value = '' , newline = '\n ' ):
29
+ initial_value = initial_value .encode ('utf-8' )
30
+ super ().__init__ (io .BufferedWriter (io .BytesIO (initial_value )),
31
+ 'utf-8' , newline = newline )
32
+
33
+ def getvalue (self ):
34
+ self .flush ()
35
+ return self .buffer .raw .getvalue ().decode ('utf-8' )
36
+
20
37
21
38
class TestCase (unittest .TestCase ):
22
39
@@ -43,11 +60,14 @@ def tearDown(self):
43
60
os .chmod (os .path .join (self .temp_dir , name ), stat .S_IWRITE )
44
61
shutil .rmtree (self .temp_dir , True )
45
62
46
- def create_readonly_file (self , filename ):
63
+ def create_writable_file (self , filename ):
47
64
file_path = os .path .join (self .temp_dir , filename )
48
65
with open (file_path , 'w' , encoding = "utf-8" ) as file :
49
66
file .write (filename )
50
- os .chmod (file_path , stat .S_IREAD )
67
+ return file_path
68
+
69
+ def create_readonly_file (self , filename ):
70
+ os .chmod (self .create_writable_file (filename ), stat .S_IREAD )
51
71
52
72
class Sig (object ):
53
73
@@ -97,10 +117,15 @@ def stderr_to_parser_error(parse_args, *args, **kwargs):
97
117
try :
98
118
result = parse_args (* args , ** kwargs )
99
119
for key in list (vars (result )):
100
- if getattr (result , key ) is sys .stdout :
120
+ attr = getattr (result , key )
121
+ if attr is sys .stdout :
101
122
setattr (result , key , old_stdout )
102
- if getattr (result , key ) is sys .stderr :
123
+ elif attr is sys .stdout .buffer :
124
+ setattr (result , key , getattr (old_stdout , 'buffer' , BIN_STDOUT_SENTINEL ))
125
+ elif attr is sys .stderr :
103
126
setattr (result , key , old_stderr )
127
+ elif attr is sys .stderr .buffer :
128
+ setattr (result , key , getattr (old_stderr , 'buffer' , BIN_STDERR_SENTINEL ))
104
129
return result
105
130
except SystemExit as e :
106
131
code = e .code
@@ -1565,16 +1590,40 @@ def test_r_1_replace(self):
1565
1590
type = argparse .FileType ('r' , 1 , errors = 'replace' )
1566
1591
self .assertEqual ("FileType('r', 1, errors='replace')" , repr (type ))
1567
1592
1593
+
1594
+ BIN_STDOUT_SENTINEL = object ()
1595
+ BIN_STDERR_SENTINEL = object ()
1596
+
1597
+
1568
1598
class StdStreamComparer :
1569
1599
def __init__ (self , attr ):
1570
- self .attr = attr
1600
+ # We try to use the actual stdXXX.buffer attribute as our
1601
+ # marker, but but under some test environments,
1602
+ # sys.stdout/err are replaced by io.StringIO which won't have .buffer,
1603
+ # so we use a sentinel simply to show that the tests do the right thing
1604
+ # for any buffer supporting object
1605
+ self .getattr = operator .attrgetter (attr )
1606
+ if attr == 'stdout.buffer' :
1607
+ self .backupattr = BIN_STDOUT_SENTINEL
1608
+ elif attr == 'stderr.buffer' :
1609
+ self .backupattr = BIN_STDERR_SENTINEL
1610
+ else :
1611
+ self .backupattr = object () # Not equal to anything
1571
1612
1572
1613
def __eq__ (self , other ):
1573
- return other == getattr (sys , self .attr )
1614
+ try :
1615
+ return other == self .getattr (sys )
1616
+ except AttributeError :
1617
+ return other == self .backupattr
1618
+
1574
1619
1575
1620
eq_stdin = StdStreamComparer ('stdin' )
1576
1621
eq_stdout = StdStreamComparer ('stdout' )
1577
1622
eq_stderr = StdStreamComparer ('stderr' )
1623
+ eq_bstdin = StdStreamComparer ('stdin.buffer' )
1624
+ eq_bstdout = StdStreamComparer ('stdout.buffer' )
1625
+ eq_bstderr = StdStreamComparer ('stderr.buffer' )
1626
+
1578
1627
1579
1628
class RFile (object ):
1580
1629
seen = {}
@@ -1653,7 +1702,7 @@ def setUp(self):
1653
1702
('foo' , NS (x = None , spam = RFile ('foo' ))),
1654
1703
('-x foo bar' , NS (x = RFile ('foo' ), spam = RFile ('bar' ))),
1655
1704
('bar -x foo' , NS (x = RFile ('foo' ), spam = RFile ('bar' ))),
1656
- ('-x - -' , NS (x = eq_stdin , spam = eq_stdin )),
1705
+ ('-x - -' , NS (x = eq_bstdin , spam = eq_bstdin )),
1657
1706
]
1658
1707
1659
1708
@@ -1680,8 +1729,9 @@ class TestFileTypeW(TempDirMixin, ParserTestCase):
1680
1729
"""Test the FileType option/argument type for writing files"""
1681
1730
1682
1731
def setUp (self ):
1683
- super (TestFileTypeW , self ).setUp ()
1732
+ super ().setUp ()
1684
1733
self .create_readonly_file ('readonly' )
1734
+ self .create_writable_file ('writable' )
1685
1735
1686
1736
argument_signatures = [
1687
1737
Sig ('-x' , type = argparse .FileType ('w' )),
@@ -1690,13 +1740,37 @@ def setUp(self):
1690
1740
failures = ['-x' , '' , 'readonly' ]
1691
1741
successes = [
1692
1742
('foo' , NS (x = None , spam = WFile ('foo' ))),
1743
+ ('writable' , NS (x = None , spam = WFile ('writable' ))),
1693
1744
('-x foo bar' , NS (x = WFile ('foo' ), spam = WFile ('bar' ))),
1694
1745
('bar -x foo' , NS (x = WFile ('foo' ), spam = WFile ('bar' ))),
1695
1746
('-x - -' , NS (x = eq_stdout , spam = eq_stdout )),
1696
1747
]
1697
1748
1749
+ @unittest .skipIf (hasattr (os , 'geteuid' ) and os .geteuid () == 0 ,
1750
+ "non-root user required" )
1751
+ class TestFileTypeX (TempDirMixin , ParserTestCase ):
1752
+ """Test the FileType option/argument type for writing new files only"""
1753
+
1754
+ def setUp (self ):
1755
+ super ().setUp ()
1756
+ self .create_readonly_file ('readonly' )
1757
+ self .create_writable_file ('writable' )
1758
+
1759
+ argument_signatures = [
1760
+ Sig ('-x' , type = argparse .FileType ('x' )),
1761
+ Sig ('spam' , type = argparse .FileType ('x' )),
1762
+ ]
1763
+ failures = ['-x' , '' , 'readonly' , 'writable' ]
1764
+ successes = [
1765
+ ('-x foo bar' , NS (x = WFile ('foo' ), spam = WFile ('bar' ))),
1766
+ ('-x - -' , NS (x = eq_stdout , spam = eq_stdout )),
1767
+ ]
1768
+
1698
1769
1770
+ @unittest .skipIf (hasattr (os , 'geteuid' ) and os .geteuid () == 0 ,
1771
+ "non-root user required" )
1699
1772
class TestFileTypeWB (TempDirMixin , ParserTestCase ):
1773
+ """Test the FileType option/argument type for writing binary files"""
1700
1774
1701
1775
argument_signatures = [
1702
1776
Sig ('-x' , type = argparse .FileType ('wb' )),
@@ -1707,7 +1781,22 @@ class TestFileTypeWB(TempDirMixin, ParserTestCase):
1707
1781
('foo' , NS (x = None , spam = WFile ('foo' ))),
1708
1782
('-x foo bar' , NS (x = WFile ('foo' ), spam = WFile ('bar' ))),
1709
1783
('bar -x foo' , NS (x = WFile ('foo' ), spam = WFile ('bar' ))),
1710
- ('-x - -' , NS (x = eq_stdout , spam = eq_stdout )),
1784
+ ('-x - -' , NS (x = eq_bstdout , spam = eq_bstdout )),
1785
+ ]
1786
+
1787
+
1788
+ @unittest .skipIf (hasattr (os , 'geteuid' ) and os .geteuid () == 0 ,
1789
+ "non-root user required" )
1790
+ class TestFileTypeXB (TestFileTypeX ):
1791
+ "Test the FileType option/argument type for writing new binary files only"
1792
+
1793
+ argument_signatures = [
1794
+ Sig ('-x' , type = argparse .FileType ('xb' )),
1795
+ Sig ('spam' , type = argparse .FileType ('xb' )),
1796
+ ]
1797
+ successes = [
1798
+ ('-x foo bar' , NS (x = WFile ('foo' ), spam = WFile ('bar' ))),
1799
+ ('-x - -' , NS (x = eq_bstdout , spam = eq_bstdout )),
1711
1800
]
1712
1801
1713
1802
0 commit comments