Skip to content

Commit 6ec75f8

Browse files
authored
Merge pull request #331 from rstudio/vetiver-tests
Vetiver tests
2 parents ac9860b + 65a5d45 commit 6ec75f8

File tree

11 files changed

+232
-0
lines changed

11 files changed

+232
-0
lines changed

.github/workflows/main.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,29 @@ jobs:
146146
repo: 'rsconnect-jupyter',
147147
event_type: 'rsconnect_python_latest'
148148
})
149+
test-rsconnect:
150+
name: "Test vetiver"
151+
runs-on: ubuntu-latest
152+
steps:
153+
- uses: actions/checkout@v2
154+
- uses: actions/setup-python@v2
155+
with:
156+
python-version: 3.8
157+
- name: Install dependencies
158+
run: |
159+
python -m pip install --upgrade pip
160+
python -m pip install -r requirements.txt
161+
python -m pip install -r vetiver-testing/vetiver-requirements.txt
162+
- name: run RStudio Connect
163+
run: |
164+
docker-compose up --build -d
165+
pip freeze > requirements.txt
166+
make dev
167+
env:
168+
RSC_LICENSE: ${{ secrets.RSC_LICENSE }}
169+
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
170+
171+
# NOTE: edited to run checks for python package
172+
- name: Run tests
173+
run: |
174+
pytest -m 'vetiver'

Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ endif
2020

2121
TEST_ENV =
2222

23+
RSC_API_KEYS=vetiver-testing/rsconnect_api_keys.json
24+
2325
ifneq ($(CONNECT_SERVER),)
2426
TEST_ENV += CONNECT_SERVER=$(CONNECT_SERVER)
2527
endif
@@ -165,3 +167,21 @@ promote-docs-in-s3:
165167
--cache-control max-age=300 \
166168
docs/site/ \
167169
s3://docs.rstudio.com/rsconnect-python/
170+
171+
172+
dev: vetiver-testing/rsconnect_api_keys.json
173+
174+
dev-start:
175+
docker-compose up -d
176+
docker-compose exec -T rsconnect bash < vetiver-testing/setup-rsconnect/add-users.sh
177+
# curl fails with error 52 without a short sleep....
178+
sleep 5
179+
curl -s --retry 10 --retry-connrefused http://localhost:3939
180+
181+
dev-stop:
182+
docker-compose down
183+
rm -f $(RSC_API_KEYS)
184+
185+
$(RSC_API_KEYS): dev-start
186+
python vetiver-testing/setup-rsconnect/dump_api_keys.py $@
187+

conftest.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
import sys
2+
import pytest
23

34
from os.path import abspath, dirname
45

56

67
HERE = dirname(abspath(__file__))
78
sys.path.insert(0, HERE)
9+
10+
11+
def pytest_addoption(parser):
12+
parser.addoption(
13+
"--vetiver", action="store_true", default=False, help="run vetiver tests"
14+
)
15+
16+
def pytest_configure(config):
17+
config.addinivalue_line("markers", "vetiver: test for vetiver interaction")
18+
19+
def pytest_collection_modifyitems(config, items):
20+
if config.getoption("--vetiver"):
21+
return
22+
skip_vetiver = pytest.mark.skip(reason="need --vetiver option to run")

docker-compose.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: '3.2'
2+
3+
services:
4+
5+
rsconnect:
6+
image: rstudio/rstudio-connect:latest
7+
restart: always
8+
ports:
9+
- 3939:3939
10+
volumes:
11+
- $PWD/vetiver-testing/setup-rsconnect/users.txt:/etc/users.txt
12+
- $PWD/vetiver-testing/setup-rsconnect/rstudio-connect.gcfg:/etc/rstudio-connect/rstudio-connect.gcfg
13+
# by default, mysql rounds to 4 decimals, but tests require more precision
14+
privileged: true
15+
environment:
16+
RSTUDIO_CONNECT_HASTE: "enabled"
17+
RSC_LICENSE: ${RSC_LICENSE}

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ omit = ["tests/*"]
1010

1111
[tool.setuptools_scm]
1212
write_to = "rsconnect/version.py"
13+
14+
[tool.pytest.ini_options]
15+
markers = [
16+
"vetiver: tests for vetiver",
17+
]

tests/test_vetiver_pins.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import pytest
2+
3+
vetiver = pytest.importorskip("vetiver", reason="vetiver library not installed")
4+
5+
import json # noqa
6+
import pins # noqa
7+
import pandas as pd # noqa
8+
import numpy as np # noqa
9+
10+
from pins.boards import BoardRsConnect # noqa
11+
from pins.rsconnect.api import RsConnectApi # noqa
12+
from pins.rsconnect.fs import RsConnectFs # noqa
13+
from rsconnect.api import RSConnectServer, RSConnectClient # noqa
14+
15+
RSC_SERVER_URL = "http://localhost:3939"
16+
RSC_KEYS_FNAME = "vetiver-testing/rsconnect_api_keys.json"
17+
18+
pytestmark = pytest.mark.vetiver # noqa
19+
20+
21+
def get_key(name):
22+
with open(RSC_KEYS_FNAME) as f:
23+
api_key = json.load(f)[name]
24+
return api_key
25+
26+
27+
def rsc_from_key(name):
28+
with open(RSC_KEYS_FNAME) as f:
29+
api_key = json.load(f)[name]
30+
return RsConnectApi(RSC_SERVER_URL, api_key)
31+
32+
33+
def rsc_fs_from_key(name):
34+
35+
rsc = rsc_from_key(name)
36+
37+
return RsConnectFs(rsc)
38+
39+
40+
def rsc_delete_user_content(rsc):
41+
guid = rsc.get_user()["guid"]
42+
content = rsc.get_content(owner_guid=guid)
43+
for entry in content:
44+
rsc.delete_content_item(entry["guid"])
45+
46+
47+
@pytest.fixture(scope="function")
48+
def rsc_short():
49+
# tears down content after each test
50+
fs_susan = rsc_fs_from_key("susan")
51+
52+
# delete any content that might already exist
53+
rsc_delete_user_content(fs_susan.api)
54+
55+
yield BoardRsConnect("", fs_susan, allow_pickle_read=True) # fs_susan.ls to list content
56+
57+
rsc_delete_user_content(fs_susan.api)
58+
59+
60+
def test_deploy(rsc_short):
61+
np.random.seed(500)
62+
63+
# Load data, model
64+
X_df, y = vetiver.mock.get_mock_data()
65+
model = vetiver.mock.get_mock_model().fit(X_df, y)
66+
67+
v = vetiver.VetiverModel(model=model, ptype_data=X_df, model_name="susan/model")
68+
69+
board = pins.board_rsconnect(server_url=RSC_SERVER_URL, api_key=get_key("susan"), allow_pickle_read=True)
70+
71+
vetiver.vetiver_pin_write(board=board, model=v)
72+
connect_server = RSConnectServer(url=RSC_SERVER_URL, api_key=get_key("susan"))
73+
74+
vetiver.deploy_rsconnect(
75+
connect_server=connect_server,
76+
board=board,
77+
pin_name="susan/model",
78+
title="testapivetiver",
79+
extra_files=["requirements.txt"],
80+
)
81+
82+
# get url of where content lives
83+
client = RSConnectClient(connect_server)
84+
dicts = client.content_search()
85+
rsc_api = list(filter(lambda x: x["title"] == "testapivetiver", dicts))
86+
content_url = rsc_api[0].get("content_url")
87+
88+
h = {"Authorization": 'Key {}'.format(get_key("susan"))}
89+
90+
endpoint = vetiver.vetiver_endpoint(content_url + "/predict")
91+
response = vetiver.predict(endpoint, X_df, headers=h)
92+
93+
assert isinstance(response, pd.DataFrame), response
94+
assert response.iloc[0, 0] == 44.47
95+
assert len(response) == 100
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
awk ' { system("useradd -m -s /bin/bash "$1); system("echo \""$1":"$2"\" | chpasswd"); system("id "$1) } ' /etc/users.txt
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import json
2+
import sys
3+
4+
from pins.rsconnect.api import _HackyConnect
5+
6+
OUT_FILE = sys.argv[1]
7+
8+
9+
def get_api_key(user, password, email):
10+
rsc = _HackyConnect("http://localhost:3939")
11+
12+
return rsc.create_first_admin(user, password, email).api_key
13+
14+
15+
api_keys = {
16+
"admin": get_api_key("admin", "admin0", "admin@example.com"),
17+
"susan": get_api_key("susan", "susan", "susan@example.com"),
18+
"derek": get_api_key("derek", "derek", "derek@example.com"),
19+
}
20+
21+
json.dump(api_keys, open(OUT_FILE, "w"))
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[Server]
2+
DataDir = /data
3+
Address = http://localhost:3939
4+
5+
[HTTP]
6+
Listen = :3939
7+
8+
[Authentication]
9+
Provider = pam
10+
11+
[Authorization]
12+
DefaultUserRole = publisher
13+
14+
[Python]
15+
Enabled = true
16+
Executable = /opt/python/3.8.10/bin/python
17+
Executable = /opt/python/3.9.5/bin/python
18+
19+
[RPackageRepository "CRAN"]
20+
URL = https://packagemanager.rstudio.com/cran/__linux__/bionic/latest
21+
22+
[RPackageRepository "RSPM"]
23+
URL = https://packagemanager.rstudio.com/cran/__linux__/bionic/latest
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
admin admin0
2+
test test
3+
susan susan
4+
derek derek
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pins
2+
pandas
3+
numpy
4+
vetiver
5+
pytest

0 commit comments

Comments
 (0)