11use anyhow:: { anyhow, Context , Result } ;
22use serde_json:: Value ;
3+ use sha1:: { Digest , Sha1 } ;
34use std:: {
5+ collections:: HashSet ,
46 env,
57 fs:: { self , File } ,
6- io:: { BufRead , BufReader , Seek , SeekFrom } ,
8+ io:: { BufRead , BufReader , Read , Seek , SeekFrom } ,
79 path:: { Path , PathBuf , MAIN_SEPARATOR } ,
810 process,
911 thread:: sleep,
@@ -60,6 +62,7 @@ fn watch_bep_json_file(
6062 let max_retries = 5 ;
6163 let mut retries = max_retries;
6264 let mut last_offset = 0 ;
65+ let mut uploader = Uploader :: new ( ) ;
6366
6467 ' parse_loop: loop {
6568 match parser. parse ( ) {
@@ -76,9 +79,13 @@ fn watch_bep_json_file(
7679 . filter ( |test_result| status. contains ( & test_result. overall_status . as_str ( ) ) )
7780 {
7881 for failed_test in test_summary. failed . iter ( ) {
79- if let Err ( error) =
80- upload_test_log ( dry, local_exec_root, & failed_test. uri , mode)
81- {
82+ if let Err ( error) = upload_test_log (
83+ & mut uploader,
84+ dry,
85+ local_exec_root,
86+ & failed_test. uri ,
87+ mode,
88+ ) {
8289 error ! ( "{:?}" , error) ;
8390 }
8491 }
@@ -107,16 +114,21 @@ fn watch_bep_json_file(
107114 let should_upload_bep_json_file =
108115 debug || ( monitor_flaky_tests && parser. has_overall_test_status ( "FLAKY" ) ) ;
109116 if should_upload_bep_json_file {
110- if let Err ( error) = upload_bep_json_file ( dry, build_event_json_file, mode) {
117+ if let Err ( error) = upload_bep_json_file ( & mut uploader , dry, build_event_json_file, mode) {
111118 error ! ( "{:?}" , error) ;
112119 }
113120 }
114121
115122 Ok ( ( ) )
116123}
117124
118- fn upload_bep_json_file ( dry : bool , build_event_json_file : & Path , mode : Mode ) -> Result < ( ) > {
119- upload_artifact ( dry, None , build_event_json_file, mode)
125+ fn upload_bep_json_file (
126+ uploader : & mut Uploader ,
127+ dry : bool ,
128+ build_event_json_file : & Path ,
129+ mode : Mode ,
130+ ) -> Result < ( ) > {
131+ uploader. upload_artifact ( dry, None , build_event_json_file, mode)
120132}
121133
122134fn execute_command ( dry : bool , cwd : Option < & Path > , program : & str , args : & [ & str ] ) -> Result < ( ) > {
@@ -148,19 +160,75 @@ fn execute_command(dry: bool, cwd: Option<&Path>, program: &str, args: &[&str])
148160 Ok ( ( ) )
149161}
150162
151- fn upload_artifact_buildkite ( dry : bool , cwd : Option < & Path > , artifact : & Path ) -> Result < ( ) > {
152- let artifact = artifact. display ( ) . to_string ( ) ;
153- execute_command (
154- dry,
155- cwd,
156- "buildkite-agent" ,
157- & [ "artifact" , "upload" , artifact. as_str ( ) ] ,
158- )
163+ type Sha1Digest = [ u8 ; 20 ] ;
164+
165+ fn read_entire_file ( path : & Path ) -> Result < Vec < u8 > > {
166+ let mut file = File :: open ( path) ?;
167+ let mut buf = Vec :: new ( ) ;
168+ file. read_to_end ( & mut buf) ?;
169+ Ok ( buf)
170+ }
171+
172+ fn sha1_digest ( path : & Path ) -> Sha1Digest {
173+ let buf = match read_entire_file ( path) {
174+ Ok ( buf) => buf,
175+ _ => path. display ( ) . to_string ( ) . into_bytes ( ) ,
176+ } ;
177+
178+ let mut hasher = Sha1 :: new ( ) ;
179+ hasher. update ( buf) ;
180+ let hash = hasher. finalize ( ) ;
181+ hash. into ( )
182+ }
183+
184+ struct Uploader {
185+ uploaded_digests : HashSet < Sha1Digest > ,
159186}
160187
161- fn upload_artifact ( dry : bool , cwd : Option < & Path > , artifact : & Path , mode : Mode ) -> Result < ( ) > {
162- match mode {
163- Mode :: Buildkite => upload_artifact_buildkite ( dry, cwd, artifact) ,
188+ impl Uploader {
189+ pub fn new ( ) -> Self {
190+ Self {
191+ uploaded_digests : HashSet :: new ( ) ,
192+ }
193+ }
194+
195+ pub fn upload_artifact (
196+ & mut self ,
197+ dry : bool ,
198+ cwd : Option < & Path > ,
199+ artifact : & Path ,
200+ mode : Mode ,
201+ ) -> Result < ( ) > {
202+ {
203+ let file = match cwd {
204+ Some ( cwd) => cwd. join ( artifact) ,
205+ None => PathBuf :: from ( artifact) ,
206+ } ;
207+ let digest = sha1_digest ( & file) ;
208+ if self . uploaded_digests . contains ( & digest) {
209+ return Ok ( ( ) ) ;
210+ }
211+ self . uploaded_digests . insert ( digest) ;
212+ }
213+
214+ match mode {
215+ Mode :: Buildkite => self . upload_artifact_buildkite ( dry, cwd, artifact) ,
216+ }
217+ }
218+
219+ fn upload_artifact_buildkite (
220+ & mut self ,
221+ dry : bool ,
222+ cwd : Option < & Path > ,
223+ artifact : & Path ,
224+ ) -> Result < ( ) > {
225+ let artifact = artifact. display ( ) . to_string ( ) ;
226+ execute_command (
227+ dry,
228+ cwd,
229+ "buildkite-agent" ,
230+ & [ "artifact" , "upload" , artifact. as_str ( ) ] ,
231+ )
164232 }
165233}
166234
@@ -213,6 +281,7 @@ fn uri_to_file_path(uri: &str) -> Result<PathBuf> {
213281}
214282
215283fn upload_test_log (
284+ uploader : & mut Uploader ,
216285 dry : bool ,
217286 local_exec_root : Option < & Path > ,
218287 test_log : & str ,
@@ -221,7 +290,7 @@ fn upload_test_log(
221290 let path = uri_to_file_path ( test_log) ?;
222291
223292 if let Some ( ( first, second) ) = split_path_inclusive ( & path, "testlogs" ) {
224- return upload_artifact ( dry, Some ( & first) , & second, mode) ;
293+ return uploader . upload_artifact ( dry, Some ( & first) , & second, mode) ;
225294 }
226295
227296 let artifact = if let Some ( local_exec_root) = local_exec_root {
@@ -234,7 +303,7 @@ fn upload_test_log(
234303 & path
235304 } ;
236305
237- upload_artifact ( dry, local_exec_root, & artifact, mode)
306+ uploader . upload_artifact ( dry, local_exec_root, & artifact, mode)
238307}
239308
240309#[ derive( Debug ) ]
0 commit comments