qlty_coverage/ci/
github.rs

1use 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}