qlty_coverage/ci/
github.rs1use crate::{
2 ci::CI,
3 env::{EnvSource, SystemEnv},
4};
5use regex::Regex;
6
7#[derive(Debug)]
8pub struct GitHub {
9 env: Box<dyn EnvSource>,
10}
11
12impl Default for GitHub {
13 fn default() -> Self {
14 Self {
15 env: Box::new(SystemEnv::default()),
16 }
17 }
18}
19
20impl CI for GitHub {
21 fn detect(&self) -> bool {
22 self.env.var("GITHUB_ACTIONS").unwrap_or_default() == "true"
23 }
24
25 fn ci_name(&self) -> String {
26 "GitHub".to_string()
27 }
28
29 fn ci_url(&self) -> String {
30 self.env.var("GITHUB_SERVER_URL").unwrap_or_default()
31 }
32
33 fn branch(&self) -> String {
34 match self.env.var("GITHUB_REF_TYPE") {
35 Some(ref_type) => {
36 if ref_type == "tag" {
37 "".to_string()
38 } else {
39 if let Some(ref_name) = self.env.var("GITHUB_REF_NAME") {
40 ref_name
41 } else {
42 self.env.var("GITHUB_HEAD_REF").unwrap_or_default()
43 }
44 }
45 }
46 None => "".to_string(),
47 }
48 }
49
50 fn workflow(&self) -> String {
51 self.env.var("GITHUB_WORKFLOW").unwrap_or_default()
52 }
53
54 fn job(&self) -> String {
55 self.env.var("GITHUB_JOB").unwrap_or_default()
56 }
57
58 fn build_id(&self) -> String {
59 let run_id = self.env.var("GITHUB_RUN_ID").unwrap_or_default();
60 let run_attempt = self.env.var("GITHUB_RUN_ATTEMPT").unwrap_or_default();
61
62 if !run_id.is_empty() && !run_attempt.is_empty() {
63 format!("{}:{}", run_id, run_attempt)
64 } else {
65 run_id
66 }
67 }
68
69 fn build_url(&self) -> String {
70 if self.build_id() != "" {
71 format!("{}/actions/runs/{}", self.repository_url(), self.build_id())
72 } else {
73 "".to_string()
74 }
75 }
76
77 fn pull_number(&self) -> String {
78 let head_ref = self.env.var("GITHUB_HEAD_REF").unwrap_or_default();
79 let full_ref = self.env.var("GITHUB_REF").unwrap_or_default();
80 let re = Regex::new(r"refs/pull/([0-9]+)/merge").unwrap();
81
82 if !head_ref.is_empty() {
83 match re.captures(&full_ref) {
84 Some(caps) => caps[1].to_string(),
85 None => "".to_string(),
86 }
87 } else {
88 "".to_string()
89 }
90 }
91
92 fn repository_name(&self) -> String {
93 self.env.var("GITHUB_REPOSITORY").unwrap_or_default()
94 }
95
96 fn repository_url(&self) -> String {
97 if self.repository_name() != "" {
98 format!("{}/{}", self.ci_url(), self.repository_name())
99 } else {
100 "".to_string()
101 }
102 }
103
104 fn pull_url(&self) -> String {
105 if self.pull_number() != "" {
106 format!("{}/pull/{}", self.repository_url(), self.pull_number())
107 } else {
108 "".to_string()
109 }
110 }
111
112 fn commit_sha(&self) -> String {
113 self.env.var("GITHUB_SHA").unwrap_or_default()
114 }
115}
116
117#[cfg(test)]
118mod test {
119 use super::*;
120 use std::collections::HashMap;
121
122 #[derive(Debug, Clone, Default)]
123 pub struct HashMapEnv {
124 inner: HashMap<String, String>,
125 }
126
127 impl HashMapEnv {
128 pub fn new(env: HashMap<String, String>) -> Self {
129 Self { inner: env }
130 }
131 }
132
133 impl EnvSource for HashMapEnv {
134 fn var(&self, name: &str) -> Option<String> {
135 self.inner.get(name).cloned()
136 }
137 }
138
139 #[test]
140 fn detect_ci() {
141 let ci = GitHub {
142 env: Box::new(HashMapEnv::default()),
143 };
144 assert_eq!(ci.detect(), false);
145
146 let mut env: HashMap<String, String> = HashMap::default();
147 env.insert("GITHUB_ACTIONS".to_string(), "true".to_string());
148 env.insert(
149 "GITHUB_SERVER_URL".to_string(),
150 "https://github.com".to_string(),
151 );
152 let ci = GitHub {
153 env: Box::new(HashMapEnv::new(env)),
154 };
155 assert_eq!(ci.detect(), true);
156 assert_eq!(&ci.ci_name(), "GitHub");
157 assert_eq!(&ci.ci_url(), "https://github.com");
158 }
159
160 #[test]
161 fn repository() {
162 let mut env: HashMap<String, String> = HashMap::default();
163 env.insert(
164 "GITHUB_SERVER_URL".to_string(),
165 "https://github.com".to_string(),
166 );
167 env.insert("GITHUB_REPOSITORY".to_string(), "qltyai/qlty".to_string());
168
169 let ci = GitHub {
170 env: Box::new(HashMapEnv::new(env)),
171 };
172 assert_eq!(&ci.repository_name(), "qltyai/qlty");
173 assert_eq!(&ci.repository_url(), "https://github.com/qltyai/qlty");
174 }
175
176 #[test]
177 fn branch_build() {
178 let mut env: HashMap<String, String> = HashMap::default();
179 env.insert("GITHUB_REF_TYPE".to_string(), "branch".to_string());
180 env.insert("GITHUB_REF_NAME".to_string(), "main".to_string());
181 env.insert(
182 "GITHUB_SHA".to_string(),
183 "77948d72a8b5ea21bb335e8e674bad99413da7a2".to_string(),
184 );
185
186 let ci = GitHub {
187 env: Box::new(HashMapEnv::new(env)),
188 };
189 assert_eq!(&ci.branch(), "main");
190 assert_eq!(&ci.pull_number(), "");
191 assert_eq!(&ci.pull_url(), "");
192 assert_eq!(&ci.commit_sha(), "77948d72a8b5ea21bb335e8e674bad99413da7a2");
193 }
194
195 #[test]
196 fn pull_request_build() {
197 let mut env: HashMap<String, String> = HashMap::default();
198 env.insert(
199 "GITHUB_SERVER_URL".to_string(),
200 "https://github.com".to_string(),
201 );
202 env.insert("GITHUB_REPOSITORY".to_string(), "qltyai/qlty".to_string());
203 env.insert("GITHUB_REF_TYPE".to_string(), "branch".to_string());
204 env.insert(
205 "GITHUB_HEAD_REF".to_string(),
206 "feature-branch-1".to_string(),
207 );
208 env.insert("GITHUB_REF".to_string(), "refs/pull/42/merge".to_string());
209 env.insert(
210 "GITHUB_SHA".to_string(),
211 "77948d72a8b5ea21bb335e8e674bad99413da7a2".to_string(),
212 );
213
214 let ci = GitHub {
215 env: Box::new(HashMapEnv::new(env)),
216 };
217 assert_eq!(&ci.branch(), "feature-branch-1");
218 assert_eq!(&ci.pull_number(), "42");
219 assert_eq!(&ci.pull_url(), "https://github.com/qltyai/qlty/pull/42");
220 assert_eq!(&ci.commit_sha(), "77948d72a8b5ea21bb335e8e674bad99413da7a2");
221 }
222
223 #[test]
224 fn job() {
225 let mut env: HashMap<String, String> = HashMap::default();
226 env.insert("GITHUB_WORKFLOW".to_string(), "deploy".to_string());
227 env.insert("GITHUB_JOB".to_string(), "run_tests".to_string());
228
229 let ci = GitHub {
230 env: Box::new(HashMapEnv::new(env)),
231 };
232 assert_eq!(&ci.workflow(), "deploy");
233 assert_eq!(&ci.job(), "run_tests");
234 }
235
236 #[test]
237 fn build() {
238 let mut env: HashMap<String, String> = HashMap::default();
239 env.insert(
240 "GITHUB_SERVER_URL".to_string(),
241 "https://github.com".to_string(),
242 );
243 env.insert("GITHUB_REPOSITORY".to_string(), "qltyai/qlty".to_string());
244 env.insert("GITHUB_RUN_ID".to_string(), "42".to_string());
245 env.insert("GITHUB_RUN_ATTEMPT".to_string(), "3".to_string());
246
247 let ci = GitHub {
248 env: Box::new(HashMapEnv::new(env)),
249 };
250 assert_eq!(&ci.build_id(), "42:3");
251 assert_eq!(
252 &ci.build_url(),
253 "https://github.com/qltyai/qlty/actions/runs/42:3"
254 );
255 }
256}