3
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
# See the License for the specific language governing permissions and
15
# limitations under the License.
20
from typing import Optional
25
CREDENTIALS_FILE_PATH: str
27
STREAMLIT_RELEASE_VERSION: Optional[str]
30
class TestCLIRegressions:
31
"""Suite of CLI regression tests to be run against a release build of the Streamlit library.
33
Before running, ensure that you have:
34
- An isolated environment with Streamlit installed in production mode (not development) as
35
well as pytest. This can include the current version, nightly, or local build/wheel, like
37
pip install streamlit-nightly=[nightly tag]
38
pip install lib/dist/<WHEEL_FILE>
40
- The STREAMLIT_RELEASE_VERSION environment variable must be set, such as:
41
export STREAMLIT_RELEASE_VERSION=1.5.1
43
You can then run the tests from the root of the Streamlit repository using one of the following:
44
pytest scripts/cli_regression_tests.py
45
make cli-regression-tests
47
This test suite makes use of Python's built-in assert statement. Note that assertions in the
48
form of `assert <expression>` use Pytest's assertion introspection. In some cases, a more clear
49
error message is specified manually by using `assert <expression>, <message>`. See
50
https://docs.pytest.org/en/7.0.x/how-to/assert.html#assert-details for more details.
53
@pytest.fixture(scope="module", autouse=True)
56
global CONFIG_FILE_PATH
57
CONFIG_FILE_PATH = os.path.expanduser("~/.streamlit/config.toml")
59
global CREDENTIALS_FILE_PATH
60
CREDENTIALS_FILE_PATH = os.path.expanduser("~/.streamlit/credentials.toml")
63
REPO_ROOT = os.getcwd()
65
global STREAMLIT_RELEASE_VERSION
66
STREAMLIT_RELEASE_VERSION = os.environ.get("STREAMLIT_RELEASE_VERSION", None)
68
# Ensure that there aren't any previously stored credentials
69
if os.path.exists(CREDENTIALS_FILE_PATH):
70
os.remove(CREDENTIALS_FILE_PATH)
75
# Remove testing credentials
76
if os.path.exists(CREDENTIALS_FILE_PATH):
77
os.remove(CREDENTIALS_FILE_PATH)
79
if os.path.exists(CONFIG_FILE_PATH):
80
os.remove(CONFIG_FILE_PATH)
82
self.run_command("streamlit cache clear")
84
def parameterize(self, params):
85
return params.split(" ")
87
def read_process_output(self, proc, num_lines_to_read):
91
while num_lines_read < num_lines_to_read:
92
output += proc.stdout.readline().decode("UTF-8")
97
def run_command(self, command):
98
return subprocess.check_output(self.parameterize(command)).decode("UTF-8")
100
def run_single_proc(self, command, num_lines_to_read=4):
101
proc = subprocess.Popen(
104
stdout=subprocess.PIPE,
105
stderr=subprocess.STDOUT,
106
preexec_fn=os.setpgrp,
109
output = self.read_process_output(proc, num_lines_to_read)
112
os.kill(os.getpgid(proc.pid), signal.SIGTERM)
113
except ProcessLookupError:
114
# The process may have exited already. If so, we don't need to do anything
120
self, command_one, command_two, wait_in_seconds=2, num_lines_to_read=4
122
proc_one = subprocess.Popen(
125
stdout=subprocess.PIPE,
126
stderr=subprocess.STDOUT,
127
preexec_fn=os.setpgrp,
130
# Getting the output from process one ensures the process started first
131
output_one = self.read_process_output(proc_one, num_lines_to_read)
133
proc_two = subprocess.Popen(
136
stdout=subprocess.PIPE,
137
stderr=subprocess.STDOUT,
138
preexec_fn=os.setpgrp,
141
output_two = self.read_process_output(proc_two, num_lines_to_read)
144
os.killpg(os.getpgid(proc_one.pid), signal.SIGKILL)
145
os.killpg(os.getpgid(proc_two.pid), signal.SIGKILL)
146
except ProcessLookupError:
147
# The process may have exited already. If so, we don't need to do anything
150
return output_one, output_two
153
bool(os.environ.get("SKIP_VERSION_CHECK", False)) == True,
154
reason="Skip version verification when `SKIP_VERSION_CHECK` env var is set",
156
def test_streamlit_version(self):
158
STREAMLIT_RELEASE_VERSION != None and STREAMLIT_RELEASE_VERSION != ""
159
), "You must set the $STREAMLIT_RELEASE_VERSION env variable"
160
assert STREAMLIT_RELEASE_VERSION in self.run_command(
162
), f"Package version does not match the desired version of {STREAMLIT_RELEASE_VERSION}"
164
def test_streamlit_activate(self):
165
process = subprocess.Popen(
166
"streamlit activate", stdin=subprocess.PIPE, shell=True
168
process.stdin.write(b"regressiontest@streamlit.io\n") # type: ignore
169
process.stdin.flush() # type: ignore
170
process.communicate()
172
with open(CREDENTIALS_FILE_PATH) as f:
174
"regressiontest@streamlit.io" in f.read()
175
), "Email address was not found in the credentials file"
177
def test_port_reassigned(self):
178
"""When starting a new Streamlit session, it will run on port 8501 by default. If 8501 is
179
not available, it will use the next available port.
182
out_one, out_two = self.run_double_proc(
183
f"streamlit run --server.headless=true {REPO_ROOT}/examples/file_uploader.py",
184
f"streamlit run --server.headless=true {REPO_ROOT}/examples/file_uploader.py",
187
assert ":8501" in out_one, f"Incorrect port. See output:\n{out_one}"
188
assert ":8502" in out_two, f"Incorrect port. See output:\n{out_two}"
190
def test_conflicting_port(self):
191
out_one, out_two = self.run_double_proc(
192
f"streamlit run --server.headless=true {REPO_ROOT}/examples/file_uploader.py",
193
f"streamlit run --server.headless=true --server.port=8501 {REPO_ROOT}/examples/file_uploader.py",
196
assert ":8501" in out_one, f"Incorrect port. See output:\n{out_one}"
198
"Port 8501 is already in use" in out_two
199
), f"Incorrect conflict. See output:\n{out_one}"
201
def test_cli_defined_port(self):
202
out = self.run_single_proc(
203
f"streamlit run --server.headless=true --server.port=9999 {REPO_ROOT}/examples/file_uploader.py",
206
assert ":9999" in out, f"Incorrect port. See output:\n{out}"
208
def test_config_toml_defined_port(self):
209
with open(CONFIG_FILE_PATH, "w") as file:
210
file.write("[server]\n port=8888")
212
out = self.run_single_proc(
213
f"streamlit run --server.headless=true {REPO_ROOT}/examples/file_uploader.py",
216
assert ":8888" in out, f"Incorrect port. See output:\n{out}"