1# Copyright 2020 pydicom authors. See LICENSE file for details. 2"""Tests for command-line interface""" 3 4from argparse import ArgumentTypeError 5 6import pytest 7 8from pydicom.cli.main import ( 9 filespec_parser, eval_element, main, filespec_parts 10) 11 12 13bad_elem_specs = ( 14 "extra:colon", 15 "no_callable()", 16 "no_equals = ", 17 "BeamSequence[0]extra", # must match to end of string 18 "BeamSequence[x]", # index must be an int 19) 20 21missing_elements = ( 22 "NotThere", 23 "BeamSequenceXX", 24 "BeamDose", # valid keyword but not at top level 25) 26 27bad_indexes = ( 28 "BeamSequence[42]", 29 "BeamSequence[-42]", 30) 31 32 33class TestFilespec: 34 @pytest.mark.parametrize("bad_spec", bad_elem_specs) 35 def test_syntax(self, bad_spec): 36 """Invalid syntax for for CLI file:element spec raises error""" 37 with pytest.raises(ArgumentTypeError, match=r".* syntax .*"): 38 filespec_parser(f"pydicom::rtplan.dcm::{bad_spec}") 39 40 @pytest.mark.parametrize("missing_element", missing_elements) 41 def test_elem_not_exists(self, missing_element): 42 """CLI filespec elements not in the dataset raise an error""" 43 with pytest.raises( 44 ArgumentTypeError, match=r".* is not in the dataset" 45 ): 46 filespec_parser(f"pydicom::rtplan.dcm::{missing_element}") 47 48 @pytest.mark.parametrize("bad_index", bad_indexes) 49 def test_bad_index(self, bad_index): 50 """CLI filespec elements with an invalid index raise an error""" 51 with pytest.raises(ArgumentTypeError, match=r".* index error"): 52 filespec_parser(f"pydicom::rtplan.dcm::{bad_index}") 53 54 def test_offers_pydicom_testfile(self): 55 """CLI message offers pydicom data file if file not found""" 56 with pytest.raises( 57 ArgumentTypeError, match=r".*pydicom::rtplan\.dcm.*is available.*" 58 ): 59 filespec_parser(f"rtplan.dcm") 60 61 def test_colons(self): 62 """CLI filespec with a colon in filename works correctly""" 63 expected = ("", r"c:\test.dcm", "") 64 assert expected == filespec_parts(r"c:\test.dcm") 65 66 expected = ("pydicom", r"c:\test.dcm", "") 67 assert expected == filespec_parts(r"pydicom::c:\test.dcm") 68 69 filespec = r"pydicom::c:\test.dcm::StudyDate" 70 expected = ("pydicom", r"c:\test.dcm", "StudyDate") 71 assert expected == filespec_parts(filespec) 72 73 filespec = r"c:\test.dcm::StudyDate" 74 expected = ("", r"c:\test.dcm", "StudyDate") 75 assert expected == filespec_parts(filespec) 76 77 78class TestFilespecElementEval: 79 # Load plan once 80 plan, _ = filespec_parser("pydicom::rtplan.dcm")[0] 81 82 def test_correct_values(self): 83 """CLI produces correct evaluation of requested element""" 84 # A nested data element 85 elem_str = "BeamSequence[0].ControlPointSequence[0].NominalBeamEnergy" 86 elem_val = eval_element(self.plan, elem_str) 87 assert 6.0 == elem_val 88 89 # A nested Sequence item 90 elem_str = "BeamSequence[0].ControlPointSequence[0]" 91 elem_val = eval_element(self.plan, elem_str) 92 assert 6.0 == elem_val.NominalBeamEnergy 93 94 # A nested Sequence itself 95 elem_str = "BeamSequence[0].ControlPointSequence" 96 elem_val = eval_element(self.plan, elem_str) 97 assert 6.0 == elem_val[0].NominalBeamEnergy 98 99 # A non-nested data element 100 elem_str = "PatientID" 101 elem_val = eval_element(self.plan, elem_str) 102 assert "id00001" == elem_val 103 104 # The file_meta or file_meta data element 105 elem_str = "file_meta" 106 elem_val = eval_element(self.plan, elem_str) 107 assert "RT Plan Storage" == elem_val.MediaStorageSOPClassUID.name 108 109 elem_str = "file_meta.MediaStorageSOPClassUID" 110 elem_val = eval_element(self.plan, elem_str) 111 assert "RT Plan Storage" == elem_val.name 112 113 114class TestCLIcall: 115 """Test calls to `pydicom` command-line interface""" 116 117 def test_bare_command(self, capsys): 118 """CLI `pydicom` with no arguments displays help""" 119 main([]) 120 out, _ = capsys.readouterr() 121 assert out.startswith("usage: pydicom [-h] {") 122 123 def test_codify_command(self, capsys): 124 """CLI `codify` command prints correct output""" 125 126 # With private elements 127 main("codify -p pydicom::nested_priv_SQ.dcm".split()) 128 out, _ = capsys.readouterr() 129 assert "add_new((0x0001, 0x0001)" in out 130 131 # Without private elements 132 main("codify pydicom::nested_priv_SQ.dcm".split()) 133 out, _ = capsys.readouterr() 134 assert "add_new((0x0001, 0x0001)" not in out 135 136 def test_codify_data_element(self, capsys): 137 """CLI `codify` command raises error if not a Dataset""" 138 with pytest.raises(NotImplementedError): 139 main("codify pydicom::rtplan.dcm::RTPlanLabel".split()) 140 141 def test_help(self, capsys): 142 """CLI `help` command gives expected output""" 143 # With subcommand 144 main("help show".split()) 145 out, err = capsys.readouterr() 146 assert out.startswith("usage: pydicom show [-h] [") 147 assert err == "" 148 149 # No subcommand following 150 main(["help"]) 151 out, _ = capsys.readouterr() 152 assert "Available subcommands:" in out 153 154 # Non-existent subcommand following 155 main("help DoesntExist".split()) 156 out, _ = capsys.readouterr() 157 assert "Available subcommands:" in out 158 159 def test_show_command(self, capsys): 160 """CLI `show` command prints correct output""" 161 main("show pydicom::MR_small_RLE.dcm".split()) 162 out, err = capsys.readouterr() 163 164 assert "Instance Creation Date DA: '20040826'" in out 165 assert out.endswith("OB: Array of 126 elements\n") 166 assert err == "" 167 168 # Get a specific data element 169 main("show pydicom::MR_small_RLE.dcm::LargestImagePixelValue".split()) 170 out, _ = capsys.readouterr() 171 assert "4000" == out.strip() 172 173 def test_show_options(self, capsys): 174 """CLI `show` command with options prints correct output""" 175 # Quiet option, image file 176 main("show -q pydicom::MR_small_RLE.dcm".split()) 177 out, err = capsys.readouterr() 178 179 assert out.startswith("SOPClassUID: MR Image Storage") 180 assert out.endswith("Rows: 64\nColumns: 64\nSliceLocation: 0.0000\n") 181 assert err == "" 182 183 # 'Quiet' option, RTPLAN file 184 main("show -q pydicom::rtplan.dcm".split()) 185 out, err = capsys.readouterr() 186 assert out.endswith( 187 "Beam 1 'Field 1' TREATMENT STATIC PHOTON energy 6.00000000000000 " 188 "gantry 0.0, coll 0.0, couch 0.0 " 189 "(0 wedges, 0 comps, 0 boli, 0 blocks)\n" 190 ) 191 assert err == "" 192 193 # Top-level-only option, also different file for more variety 194 main("show -t pydicom::nested_priv_SQ.dcm".split()) 195 out, err = capsys.readouterr() 196 assert "(0001, 0001) Private Creator" in out 197 assert "UN: b'Nested SQ'" not in out 198 assert err == "" 199 200 # Exclude private option 201 main("show -x pydicom::nested_priv_SQ.dcm".split()) 202 out, err = capsys.readouterr() 203 assert "(0001, 0001) Private Creator" not in out 204 assert err == "" 205