Skip to content

Commit 42e38e0

Browse files
committed
Add the gitlab prefix masking tests directly
This uses the actual source code of those tests, which is MIT licensed (as is this crate), to be certain that we match the behaviour of the upstream runner.
1 parent fda0bc2 commit 42e38e0

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

gitlab-runner/tests/masking.rs

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
use futures::AsyncWriteExt;
2+
use gitlab_runner::uploader::Uploader;
3+
use gitlab_runner::{outputln, JobHandler, JobResult, Phase, Runner};
4+
use gitlab_runner_mock::{GitlabRunnerMock, MockJobState, MockJobStepName, MockJobStepWhen};
5+
use std::io::{Cursor, Read};
6+
use tracing::instrument::WithSubscriber;
7+
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
8+
use tracing_subscriber::Registry;
9+
use zip::ZipArchive;
10+
11+
struct MaskTest {
12+
log_text: String,
13+
}
14+
15+
#[async_trait::async_trait]
16+
impl JobHandler for MaskTest {
17+
async fn step(&mut self, _script: &[String], _phase: Phase) -> JobResult {
18+
outputln!("{}", self.log_text.split('|').collect::<Vec<_>>().join(""));
19+
Ok(())
20+
}
21+
22+
async fn upload_artifacts(&mut self, uploader: &mut Uploader) -> JobResult {
23+
let mut f = uploader.masked_file("masked".to_string()).await;
24+
f.write_all(
25+
format!(
26+
"{}\n",
27+
self.log_text.split('|').collect::<Vec<_>>().join("")
28+
)
29+
.as_bytes(),
30+
)
31+
.await
32+
.expect("Couldn't write test data");
33+
drop(f);
34+
let mut f = uploader.file("baseline".to_string()).await;
35+
f.write_all(self.log_text.as_bytes())
36+
.await
37+
.expect("Couldn't write test data");
38+
39+
Ok(())
40+
}
41+
}
42+
43+
#[derive(Debug, Clone, Default)]
44+
struct TestCase {
45+
prefixes: Vec<String>,
46+
input: String,
47+
expected: String,
48+
name: String,
49+
}
50+
51+
macro_rules! emit_prefixes {
52+
( $( $pfx:expr ),* ) => {
53+
vec![
54+
$(
55+
$pfx.to_string()
56+
),*
57+
]
58+
};
59+
}
60+
61+
macro_rules! parse_test_case {
62+
( $data:expr, ) => {};
63+
64+
( $data:expr, $name:literal : { input: $input:expr, expected: $expected:expr, } ) => {
65+
$data.push( TestCase { prefixes : Vec::new(), input: $input.to_string(), expected: format!("{}\n", $expected), name: $name.to_string() } );
66+
};
67+
68+
( $data:expr, $name:literal : { input: $input:expr, expected: $expected:expr, }, $($tail:tt)+ ) => {
69+
$data.push( TestCase { prefixes : Vec::new(), input: $input.to_string(), expected: format!("{}\n", $expected), name: $name.to_string() } );
70+
parse_test_case![$data, $($tail)*]
71+
};
72+
73+
( $data:expr, $name:literal : { prefixes: []string{ $($pfx:expr),* }, input: $input:expr, expected: $expected:expr, } ) => {
74+
$data.push( TestCase { prefixes : emit_prefixes![$($pfx),*], input: $input.to_string(), expected: format!("{}\n", $expected), name: $name.to_string() } );
75+
};
76+
77+
( $data:expr, $name:literal : { prefixes: []string{ $($pfx:expr),* }, input: $input:expr, expected: $expected:expr, }, $($tail:tt)* ) => {
78+
$data.push( TestCase { prefixes : emit_prefixes![$($pfx),*], input: $input.to_string(), expected: format!("{}\n", $expected), name: $name.to_string() } );
79+
parse_test_case![$data, $($tail)*]
80+
};
81+
}
82+
83+
macro_rules! test_cases {
84+
() => {};
85+
86+
( $($tail:tt)* ) => {
87+
{
88+
let mut data = Vec::new();
89+
parse_test_case!(data, $($tail)+);
90+
data
91+
}
92+
};
93+
}
94+
95+
#[tokio::test]
96+
async fn mask_test() {
97+
// These test data are taken directly from the official
98+
// gitlab-runner source, and are still formatted for Go. We use a
99+
// simple pair of macros to convert them into an appropriate Rust
100+
// data structure.
101+
let test_cases = test_cases![
102+
"simple prefix masking": {
103+
input: "Lorem ipsum dolor sit amet, ex ea commodo glpat-imperdiet in voluptate velit esse",
104+
expected: "Lorem ipsum dolor sit amet, ex ea commodo glpat-[MASKED] in voluptate velit esse",
105+
},
106+
"prefix at the end of the line": {
107+
input: "Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esseglpat-imperdiet",
108+
expected: "Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esseglpat-[MASKED]",
109+
},
110+
"prefix at the beginning of the line": {
111+
input: "glpat-imperdiet Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse",
112+
expected: "glpat-[MASKED] Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse",
113+
},
114+
"prefix inside of the line": {
115+
input: "esseglpat-imperdiet=_-. end Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit",
116+
expected: "esseglpat-[MASKED] end Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit",
117+
},
118+
"two prefix concatenate": {
119+
input: "glpat-impglpat-erdiet Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse",
120+
expected: "glpat-[MASKED] Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse",
121+
},
122+
"multiple packets pat masking": {
123+
input: "glpat|-imperdiet Lorem ipsum dolor sit amet, ex ea commodo gl|pat-imperdiet in voluptate velit esse",
124+
expected: "glpat-[MASKED] Lorem ipsum dolor sit amet, ex ea commodo glpat-[MASKED] in voluptate velit esse",
125+
},
126+
"second multiple packets pat masking": {
127+
input: "glpat| -imperdiet Lorem ipsum dolor sit amet",
128+
expected: "glpat -imperdiet Lorem ipsum dolor sit amet",
129+
},
130+
"long input": {
131+
input: "Lorglpat-ipsu dolor sit amglpat-t, consglpat-ctglpat-tur adipiscing glpat-lit, sglpat-d do glpat-iusmod tglpat-mpor incididunt ut laborglpat-=_ glpat-t dolorglpat-=_ magna aliqua.",
132+
expected: "Lorglpat-[MASKED] dolor sit amglpat-[MASKED], consglpat-[MASKED] adipiscing glpat-[MASKED], sglpat-[MASKED] do glpat-[MASKED] tglpat-[MASKED] incididunt ut laborglpat-[MASKED] glpat-[MASKED] dolorglpat-[MASKED] magna aliqua.",
133+
},
134+
"multiple packets long input": {
135+
input: "Lorglpat-ipsu dolor sit amglp|at-t, consglpat-ctg|lpat-tur adipiscing glpat-lit, sglpat-|d do glpat-iusmod t|glpat-mpor incididunt ut |laborglpat-=_ glpat-t dolorglpat-=_ magna aliqua.",
136+
expected: "Lorglpat-[MASKED] dolor sit amglpat-[MASKED], consglpat-[MASKED] adipiscing glpat-[MASKED], sglpat-[MASKED] do glpat-[MASKED] tglpat-[MASKED] incididunt ut laborglpat-[MASKED] glpat-[MASKED] dolorglpat-[MASKED] magna aliqua.",
137+
},
138+
"second long input": {
139+
input: "Lorglpat- ipsu dolor sit amglpat-t, consglpat-ctglpat-tur adipiscing glpat-lit, sglpat-d do glpat-iusmod tglpat-mpor incididunt ut laborglpat-=_ glpat-t dolorglpat-=_ magna aliqua.",
140+
expected: "Lorglpat- ipsu dolor sit amglpat-[MASKED], consglpat-[MASKED] adipiscing glpat-[MASKED], sglpat-[MASKED] do glpat-[MASKED] tglpat-[MASKED] incididunt ut laborglpat-[MASKED] glpat-[MASKED] dolorglpat-[MASKED] magna aliqua.",
141+
},
142+
"custom prefix with default one at the beginning of the line": {
143+
prefixes: []string{"token-"},
144+
input: "token-imperdiet Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse",
145+
expected: "token-[MASKED] Lorem ipsum dolor sit amet, ex ea commodo in voluptate velit esse",
146+
},
147+
"custom prefix with default one multiple packets long input": {
148+
prefixes: []string{"tok-"},
149+
input: "Lortok-ipsu dolor sit amt|ok-t, cons-ctg|lpat-tur adipiscing tok-lit, stok-|d do tok-iusmod t|tok-mpor incididunt ut |labortok-=_ tok-t dolortok-=_ magna aliqua. Tglpat-llus orci ac auctor auguglpat-eee mauris auguglpat-wEr_ lorem",
150+
expected: "Lortok-[MASKED] dolor sit amtok-[MASKED], cons-ctglpat-[MASKED] adipiscing tok-[MASKED], stok-[MASKED] do tok-[MASKED] ttok-[MASKED] incididunt ut labortok-[MASKED] tok-[MASKED] dolortok-[MASKED] magna aliqua. Tglpat-[MASKED] orci ac auctor auguglpat-[MASKED] mauris auguglpat-[MASKED] lorem",
151+
},
152+
"ignored eleventh prefix and more": {
153+
prefixes: []string{"mask1-", "mask2-", "mask3-", "mask4-", "mask5-", "mask6-", "mask7-", "mask8-", "mask9-", "mask10-", "mask11-"},
154+
input: "Lormask1-ipsu dolor sit amm|ask2-t, cons-ctg|lpat-tur adipiscing mask5-lit, smask11-|d do mask7-iusmod t|glpat-mpor incididunt ut |labormask10-=_ mask9-t",
155+
expected: "Lormask1-[MASKED] dolor sit ammask2-[MASKED], cons-ctglpat-[MASKED] adipiscing mask5-[MASKED], smask11-d do mask7-[MASKED] tglpat-[MASKED] incididunt ut labormask10-=_ mask9-[MASKED]",
156+
},
157+
"whitespaced prefixes": {
158+
prefixes: []string{" mask1- ", " mask2-", "mask3- ", "mask4-", "mask5-", "mask6-", "mask7-", "mask8-", "mask9-"},
159+
input: "Lormask1-ipsu dolor sit amm|ask2-t, cons-ctg|lpat-tur adipiscing mask5-lit, smask11-|d do mask7-iusmod t|glpat-mpor incididunt ut |labormask10-=_ mask9-t",
160+
expected: "Lormask1-[MASKED] dolor sit ammask2-[MASKED], cons-ctglpat-[MASKED] adipiscing mask5-[MASKED], smask11-d do mask7-[MASKED] tglpat-[MASKED] incididunt ut labormask10-=_ mask9-[MASKED]",
161+
},
162+
];
163+
164+
let mock = GitlabRunnerMock::start().await;
165+
166+
for t in test_cases {
167+
println!("{}", t.name);
168+
let mut log_job = mock.job_builder(t.name.to_string());
169+
log_job.add_step(
170+
MockJobStepName::Script,
171+
vec!["dummy".to_string()],
172+
3600,
173+
MockJobStepWhen::OnSuccess,
174+
false,
175+
);
176+
177+
log_job.add_token_prefix("glpat-".to_string());
178+
for pfx in t.prefixes.iter().take(9) {
179+
log_job.add_token_prefix(pfx.to_string());
180+
}
181+
182+
log_job.add_artifact_paths(vec!["*".to_string()]);
183+
let log_job = log_job.build();
184+
mock.enqueue_job(log_job.clone());
185+
186+
let dir = tempfile::tempdir().unwrap();
187+
188+
let (mut runner, layer) = Runner::new_with_layer(
189+
mock.uri(),
190+
mock.runner_token().to_string(),
191+
dir.path().to_path_buf(),
192+
);
193+
194+
let subscriber = Registry::default().with(layer);
195+
async {
196+
// Upload job comes first
197+
let inp = t.input.clone();
198+
let got_job = runner
199+
.request_job(|_job| async move { Ok(MaskTest { log_text: inp }) })
200+
.await
201+
.unwrap();
202+
203+
assert!(got_job);
204+
runner.wait_for_space(1).await;
205+
assert_eq!(MockJobState::Success, log_job.state());
206+
207+
let data = log_job.log();
208+
println!(
209+
"IN: {}\n LEFT: {} RIGHT: {}",
210+
t.input,
211+
String::from_utf8_lossy(data.as_slice()),
212+
t.expected
213+
);
214+
assert_eq!(data.as_slice(), t.expected.as_bytes());
215+
}
216+
.with_subscriber(subscriber)
217+
.await;
218+
219+
let data = {
220+
let raw = mock
221+
.get_job_artifact(log_job.id())
222+
.expect("failed to get job artifact");
223+
let mut z =
224+
ZipArchive::new(Cursor::new(raw)).expect("failed to open job artifact as zip");
225+
let mut f = z
226+
.by_name("masked")
227+
.expect("failed to obtain masked log file");
228+
let mut data = Vec::new();
229+
f.read_to_end(&mut data)
230+
.expect("failed to read data from masked log file");
231+
data
232+
};
233+
234+
println!(
235+
"IN: {}\n LEFT: {} RIGHT: {}",
236+
t.input,
237+
String::from_utf8_lossy(data.as_slice()),
238+
t.expected
239+
);
240+
assert_eq!(data.as_slice(), t.expected.as_bytes());
241+
}
242+
}

0 commit comments

Comments
 (0)