1use std::{
2 collections::HashMap,
3 fmt::Debug,
4 fs::{File, Metadata, read},
5 io::{Read, Seek, Write},
6 path::{Path, PathBuf},
7 time::{Duration, SystemTime},
8};
9
10use cargo_lambda_metadata::{
11 cargo::{CargoMetadata, target_dir_from_metadata},
12 fs::copy_and_replace,
13};
14use cargo_lambda_remote::aws_sdk_lambda::types::Architecture as CpuArchitecture;
15use chrono::{DateTime, NaiveDateTime, Utc};
16use chrono_humanize::HumanTime;
17use miette::{Context, IntoDiagnostic, Result};
18use object::{Architecture, Object, read::File as ObjectFile};
19use serde::{Serialize, Serializer};
20use sha2::{Digest, Sha256};
21use tracing::{debug, trace};
22use walkdir::WalkDir;
23use zip::{HasZipMetadata, ZipArchive, ZipWriter, write::SimpleFileOptions};
24
25use crate::error::BuildError;
26
27#[derive(Clone, Debug)]
28pub struct BinaryModifiedAt(Option<SystemTime>);
29
30impl BinaryModifiedAt {
31 pub fn now() -> Self {
32 Self(Some(SystemTime::now()))
33 }
34}
35
36impl From<NaiveDateTime> for BinaryModifiedAt {
37 fn from(value: NaiveDateTime) -> Self {
38 let dt = DateTime::<Utc>::from_naive_utc_and_offset(value, Utc);
39 let Some(timestamp) = dt.timestamp_nanos_opt() else {
40 return Self(None);
41 };
42
43 let system_time = SystemTime::UNIX_EPOCH + Duration::from_nanos(timestamp as u64);
44 Self(Some(system_time))
45 }
46}
47
48#[derive(Debug)]
49pub enum BinaryData<'a> {
50 Function(&'a str),
51 ExternalExtension(&'a str),
52 InternalExtension(&'a str),
53}
54
55impl<'a> BinaryData<'a> {
56 pub fn new(name: &'a str, extension: bool, internal: bool) -> Self {
58 if extension {
59 if internal {
60 BinaryData::InternalExtension(name)
61 } else {
62 BinaryData::ExternalExtension(name)
63 }
64 } else {
65 BinaryData::Function(name)
66 }
67 }
68
69 pub fn binary_name(&self) -> &str {
71 match self {
72 BinaryData::Function(_) => "bootstrap",
73 BinaryData::ExternalExtension(name) | BinaryData::InternalExtension(name) => name,
74 }
75 }
76
77 pub fn zip_name(&self) -> String {
79 format!("{}.zip", self.binary_name())
80 }
81
82 pub fn binary_location(&self) -> &str {
84 match self {
85 BinaryData::Function(name) => name,
86 BinaryData::ExternalExtension(_) | BinaryData::InternalExtension(_) => "extensions",
87 }
88 }
89
90 pub fn parent_dir(&self) -> Option<&str> {
92 match self {
93 BinaryData::ExternalExtension(_) => Some("extensions"),
94 _ => None,
95 }
96 }
97
98 pub fn build_help(&self) -> &str {
100 match self {
101 BinaryData::Function(_) => "build",
102 BinaryData::ExternalExtension(_) => "build --extension",
103 BinaryData::InternalExtension(_) => "build --extension --internal",
104 }
105 }
106
107 pub(crate) fn binary_path_in_zip(&self) -> Result<String, BuildError> {
108 let file_name = if let Some(parent) = self.parent_dir() {
109 Path::new(parent).join(self.binary_name())
110 } else {
111 PathBuf::from(self.binary_name())
112 };
113
114 convert_to_unix_path(&file_name)
115 .ok_or_else(|| BuildError::InvalidUnixFileName(file_name.clone()))
116 }
117}
118
119pub struct BinaryArchive {
120 pub architecture: String,
121 pub path: PathBuf,
122 pub binary_modified_at: BinaryModifiedAt,
123}
124
125impl BinaryArchive {
126 pub fn new(path: PathBuf, architecture: String, binary_modified_at: BinaryModifiedAt) -> Self {
127 Self {
128 path,
129 architecture,
130 binary_modified_at,
131 }
132 }
133
134 pub fn read(&self) -> Result<Vec<u8>> {
136 read(&self.path)
137 .into_diagnostic()
138 .wrap_err("failed to read binary archive")
139 }
140
141 pub fn sha256(&self) -> Result<String> {
143 let data = self.read()?;
144 let mut hasher = Sha256::new();
145 hasher.update(data);
146 let sha256 = format!("{:X}", hasher.finalize());
147 Ok(sha256)
148 }
149
150 pub fn list(&self) -> Result<Vec<String>> {
152 let zipfile = File::open(&self.path).into_diagnostic()?;
153 let mut archive = ZipArchive::new(zipfile).into_diagnostic()?;
154
155 let mut files = Vec::new();
156 for i in 0..archive.len() {
157 let entry = archive.by_index(i).into_diagnostic()?;
158 files.push(entry.name().to_string());
159 }
160
161 Ok(files)
162 }
163
164 pub fn architecture(&self) -> CpuArchitecture {
166 CpuArchitecture::from(self.architecture.as_str())
167 }
168}
169
170pub fn create_binary_archive<P>(
175 metadata: Option<&CargoMetadata>,
176 base_dir: &Option<P>,
177 data: &BinaryData,
178 include: Option<Vec<String>>,
179) -> Result<BinaryArchive>
180where
181 P: AsRef<Path>,
182{
183 let bootstrap_dir = if let Some(dir) = base_dir {
184 dir.as_ref().join(data.binary_location())
185 } else {
186 let target_dir = metadata
187 .and_then(|m| target_dir_from_metadata(m).ok())
188 .unwrap_or_else(|| PathBuf::from("target"));
189
190 target_dir.join("lambda").join(data.binary_location())
191 };
192
193 let binary_path = bootstrap_dir.join(data.binary_name());
194 if binary_path.exists() {
195 return zip_binary(binary_path, bootstrap_dir, data, include);
196 } else {
197 let zip_path = bootstrap_dir.join(data.zip_name());
198
199 if zip_path.exists() {
200 return use_zip_in_place(zip_path, data, include);
201 }
202 }
203
204 Err(BuildError::BinaryMissing(data.binary_name().into(), data.build_help().into()).into())
205}
206
207fn use_zip_in_place(
208 zip_path: PathBuf,
209 data: &BinaryData<'_>,
210 include: Option<Vec<String>>,
211) -> Result<BinaryArchive> {
212 let binary_path_in_zip = data.binary_path_in_zip()?;
213 let (arch, binary_modified_at) =
214 extract_data_from_zipped_binary(&zip_path, &binary_path_in_zip)?;
215
216 if let Some(files) = include {
217 let file = File::open(&zip_path)
218 .into_diagnostic()
219 .wrap_err_with(|| format!("failed to open zip file `{zip_path:?}`"))?;
220
221 let mut archive = ZipArchive::new(file).into_diagnostic()?;
225
226 let tmp_dir = tempfile::tempdir().into_diagnostic()?;
227 let tmp_path = tmp_dir.path().join(data.zip_name());
228 let tmp = File::create(&tmp_path).into_diagnostic()?;
229 let mut zip = ZipWriter::new(tmp);
230
231 for i in 0..archive.len() {
232 let file = archive.by_index_raw(i).into_diagnostic()?;
233 zip.raw_copy_file(file).into_diagnostic()?;
234 }
235
236 include_files_in_zip(&mut zip, &files)?;
237
238 zip.finish()
239 .into_diagnostic()
240 .wrap_err_with(|| format!("failed to finish zip file `{}`", tmp_path.display()))?;
241
242 drop(archive);
243 copy_and_replace(&tmp_path, &zip_path).into_diagnostic()?;
244 }
245
246 Ok(BinaryArchive::new(
247 zip_path.clone(),
248 arch.to_string(),
249 binary_modified_at,
250 ))
251}
252
253pub fn zip_binary<BP: AsRef<Path>, DD: AsRef<Path>>(
258 binary_path: BP,
259 destination_directory: DD,
260 data: &BinaryData,
261 include: Option<Vec<String>>,
262) -> Result<BinaryArchive> {
263 let path = binary_path.as_ref();
264 let dir = destination_directory.as_ref();
265
266 let zipped = dir.join(data.zip_name());
267 debug!(?data, ?path, ?dir, ?zipped, "zipping binary");
268
269 let zipped_binary = File::create(&zipped)
270 .into_diagnostic()
271 .wrap_err_with(|| format!("failed to create zip file `{zipped:?}`"))?;
272
273 let mut file = File::open(path)
274 .into_diagnostic()
275 .wrap_err_with(|| format!("failed to open binary file `{path:?}`"))?;
276
277 let file_metadata = file
278 .metadata()
279 .into_diagnostic()
280 .wrap_err_with(|| format!("failed to get metadata from file `{path:?}`"))?;
281
282 let binary_modified_at = file_metadata
283 .modified()
284 .ok()
285 .or_else(|| file_metadata.created().ok());
286
287 let (binary_data, arch) = read_binary(&mut file, path)?;
288
289 let mut zip = ZipWriter::new(zipped_binary);
290 if let Some(files) = include {
291 include_files_in_zip(&mut zip, &files)?;
292 }
293
294 if let Some(parent) = data.parent_dir() {
295 let options = SimpleFileOptions::default();
296 zip.add_directory(parent, options)
297 .into_diagnostic()
298 .wrap_err_with(|| {
299 format!("failed to add directory `{parent}` to zip file `{zipped:?}`")
300 })?;
301 }
302
303 let binary_path_in_zip = data.binary_path_in_zip()?;
304
305 let options = zip_file_options(&file, path)?;
306
307 zip.start_file(binary_path_in_zip.to_string(), options)
308 .into_diagnostic()
309 .wrap_err_with(|| format!("failed to start zip file `{binary_path_in_zip:?}`"))?;
310 zip.write_all(&binary_data)
311 .into_diagnostic()
312 .wrap_err_with(|| format!("failed to write data into zip file `{binary_path_in_zip:?}`"))?;
313 zip.finish()
314 .into_diagnostic()
315 .wrap_err_with(|| format!("failed to finish zip file `{binary_path_in_zip:?}`"))?;
316
317 Ok(BinaryArchive::new(
318 zipped,
319 arch.to_string(),
320 BinaryModifiedAt(binary_modified_at),
321 ))
322}
323
324fn extract_data_from_zipped_binary<'a>(
325 zip_path: &'a Path,
326 binary_path: &'a str,
327) -> Result<(&'a str, BinaryModifiedAt)> {
328 let zipfile = File::open(zip_path).into_diagnostic()?;
329 let mut archive = ZipArchive::new(zipfile).into_diagnostic()?;
330
331 let mut file = archive.by_name(binary_path).into_diagnostic()?;
332 let (_, arch) = read_binary(&mut file, binary_path)?;
333
334 let metadata = file.get_metadata();
335 let mut last_modified_at = BinaryModifiedAt(None);
336 if let Some(dt) = metadata.last_modified_time {
337 let naive_dt: NaiveDateTime = dt.try_into().into_diagnostic()?;
338 last_modified_at = naive_dt.into();
339 }
340
341 Ok((arch, last_modified_at))
342}
343
344fn read_binary<'a, R: Read, D: std::fmt::Debug>(
345 entry: &mut R,
346 binary_path: D,
347) -> Result<(Vec<u8>, &'a str)> {
348 let mut binary_data = Vec::new();
349
350 entry
351 .read_to_end(&mut binary_data)
352 .into_diagnostic()
353 .wrap_err_with(|| format!("failed to read binary file `{binary_path:?}`"))?;
354
355 let object = ObjectFile::parse(&*binary_data)
356 .into_diagnostic()
357 .wrap_err("the provided function file is not a valid Linux binary")?;
358
359 let arch = match object.architecture() {
360 Architecture::Aarch64 => "arm64",
361 Architecture::X86_64 => "x86_64",
362 other => return Err(BuildError::InvalidBinaryArchitecture(other).into()),
363 };
364
365 Ok((binary_data, arch))
366}
367
368fn zip_file_options(file: &File, path: &Path) -> Result<SimpleFileOptions> {
369 let meta = file
370 .metadata()
371 .into_diagnostic()
372 .wrap_err_with(|| format!("failed to get metadata from file `{path:?}`"))?;
373 let perm = binary_permissions(&meta);
374 let mut options = SimpleFileOptions::default().unix_permissions(perm);
375 if let Some(mtime) = binary_mtime(&meta) {
376 options = options.last_modified_time(mtime);
377 }
378
379 Ok(options)
380}
381
382fn include_files_in_zip<W: Write + Seek>(
383 zip: &mut ZipWriter<W>,
384 files: &Vec<String>,
385) -> Result<()> {
386 let mut file_map = HashMap::with_capacity(files.len());
387 for file in files {
388 match file.split_once(':') {
389 None => file_map.insert(file.clone(), file.clone()),
390 Some((name, path)) => file_map.insert(name.into(), path.into()),
391 };
392 }
393
394 for (base, file) in file_map {
395 for entry in WalkDir::new(&file).into_iter().filter_map(|e| e.ok()) {
396 let path = entry.path();
397 let base = base.clone();
398 let file = file.clone();
399
400 let unix_base = convert_to_unix_path(Path::new(&base))
401 .ok_or_else(|| BuildError::InvalidUnixFileName(base.into()))?;
402 let unix_file = convert_to_unix_path(Path::new(&file))
403 .ok_or_else(|| BuildError::InvalidUnixFileName(file.into()))?;
404
405 let source_name = convert_to_unix_path(path)
406 .ok_or_else(|| BuildError::InvalidUnixFileName(path.into()))?;
407
408 let destination_name = source_name.replace(&unix_file, &unix_base);
409
410 if path.is_dir() {
411 trace!(%destination_name, "creating directory in zip file");
412
413 zip.add_directory(&destination_name, SimpleFileOptions::default())
414 .into_diagnostic()
415 .wrap_err_with(|| {
416 format!("failed to add directory `{destination_name}` to zip file")
417 })?;
418 } else {
419 trace!(%source_name, %destination_name, "including file in zip file");
420
421 let mut content = Vec::new();
422 let mut file = File::open(path)
423 .into_diagnostic()
424 .wrap_err_with(|| format!("failed to open file `{path:?}`"))?;
425 file.read_to_end(&mut content)
426 .into_diagnostic()
427 .wrap_err_with(|| format!("failed to read file `{path:?}`"))?;
428
429 let options = zip_file_options(&file, path)?;
430
431 zip.start_file(destination_name.clone(), options)
432 .into_diagnostic()
433 .wrap_err_with(|| {
434 format!("failed to create zip content file `{destination_name:?}`")
435 })?;
436
437 zip.write_all(&content)
438 .into_diagnostic()
439 .wrap_err_with(|| {
440 format!("failed to write data into zip content file `{destination_name:?}`")
441 })?;
442 }
443 }
444 }
445 Ok(())
446}
447
448fn binary_mtime(meta: &Metadata) -> Option<zip::DateTime> {
449 let Ok(modified) = meta.modified() else {
450 return None;
451 };
452
453 let dt: DateTime<Utc> = modified.into();
454 if let Ok(dt) = zip::DateTime::try_from(dt.naive_utc()) {
455 return Some(dt);
456 }
457
458 let Ok(created) = meta.created() else {
459 return None;
460 };
461
462 let dt: DateTime<Utc> = created.into();
463 zip::DateTime::try_from(dt.naive_utc()).ok()
464}
465
466#[cfg(unix)]
467fn binary_permissions(meta: &Metadata) -> u32 {
468 use std::os::unix::fs::PermissionsExt;
469 meta.permissions().mode()
470}
471
472#[cfg(not(unix))]
473fn binary_permissions(_meta: &Metadata) -> u32 {
474 0o755
475}
476
477#[cfg(target_os = "windows")]
478fn convert_to_unix_path(path: &Path) -> Option<String> {
479 let mut path_str = String::new();
480 for component in path.components() {
481 if let std::path::Component::Normal(os_str) = component {
482 if !path_str.is_empty() {
483 path_str.push('/');
484 }
485 path_str.push_str(os_str.to_str()?);
486 }
487 }
488 Some(path_str)
489}
490
491#[cfg(not(target_os = "windows"))]
492fn convert_to_unix_path(path: &Path) -> Option<String> {
493 path.to_str().map(String::from)
494}
495
496impl BinaryModifiedAt {
497 pub fn humanize(&self) -> String {
498 match self.0 {
499 Some(time) => HumanTime::from(time).to_string(),
500 None => "at unknown time".to_string(),
501 }
502 }
503}
504
505impl Serialize for BinaryModifiedAt {
506 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
507 where
508 S: Serializer,
509 {
510 match self.0 {
511 Some(time) => serializer.serialize_str(&HumanTime::from(time).to_string()),
512 None => serializer.serialize_none(),
513 }
514 }
515}
516
517#[cfg(test)]
518mod test {
519 use std::{
520 fs::{create_dir_all, remove_dir_all},
521 thread::sleep,
522 time::Duration,
523 };
524
525 use cargo_lambda_metadata::{cargo::load_metadata, fs::copy_without_replace};
526 use rstest::rstest;
527 use tempfile::TempDir;
528 use zip::ZipArchive;
529
530 use super::*;
531
532 #[test]
533 fn test_convert_to_unix_path() {
534 let path = Path::new("extensions").join("test").join("filename");
536 assert_eq!(
537 "extensions/test/filename",
538 convert_to_unix_path(&path).expect("failed to convert file path")
539 );
540 }
541
542 #[test]
543 fn test_convert_to_unix_path_keep_original() {
544 let path = Path::new("extensions/test/filename");
545 assert_eq!(
546 "extensions/test/filename",
547 convert_to_unix_path(path).expect("failed to convert file path")
548 );
549 }
550
551 #[test]
552 fn test_convert_to_unix_path_empty_path() {
553 let path = Path::new("");
554 assert_eq!(
555 "",
556 convert_to_unix_path(path).expect("failed to convert file path")
557 );
558 }
559
560 #[rstest]
561 #[case("binary-x86-64", "x86_64")]
562 #[case("binary-arm64", "arm64")]
563 fn test_zip_funcion(#[case] name: &str, #[case] arch: &str) {
564 let data = BinaryData::new(name, false, false);
565 let bp = &format!("../../tests/binaries/{name}");
566 let dd = TempDir::with_prefix("cargo-lambda-").expect("failed to create temp dir");
567 let archive =
568 zip_binary(bp, dd.path(), &data, None).expect("failed to create binary archive");
569
570 assert_eq!(arch, archive.architecture);
571
572 let arch_path = dd.path().join("bootstrap.zip");
573 assert_eq!(arch_path, archive.path);
574
575 let file = File::open(arch_path).expect("failed to open zip file");
576 let mut zip = ZipArchive::new(file).expect("failed to open zip archive");
577
578 zip.by_name("bootstrap")
579 .expect("failed to find bootstrap in zip archive");
580 }
581
582 #[rstest]
583 #[case("binary-x86-64", "x86_64")]
584 #[case("binary-arm64", "arm64")]
585 fn test_zip_extension(#[case] name: &str, #[case] arch: &str) {
586 let data = BinaryData::new(name, true, false);
587
588 let bp = &format!("../../tests/binaries/{name}");
589 let dd = TempDir::with_prefix("cargo-lambda-").expect("failed to create temp dir");
590 let archive =
591 zip_binary(bp, dd.path(), &data, None).expect("failed to create binary archive");
592
593 assert_eq!(arch, archive.architecture);
594
595 let arch_path = dd.path().join(format!("{name}.zip"));
596 assert_eq!(arch_path, archive.path);
597
598 let file = File::open(arch_path).expect("failed to open zip file");
599 let mut zip = ZipArchive::new(file).expect("failed to open zip archive");
600
601 zip.by_name(&format!("extensions/{name}"))
602 .expect("failed to find bootstrap in zip archive");
603 }
604
605 #[rstest]
606 #[case("binary-x86-64", "x86_64")]
607 #[case("binary-arm64", "arm64")]
608 fn test_zip_internal_extension(#[case] name: &str, #[case] arch: &str) {
609 let data = BinaryData::new(name, true, true);
610
611 let bp = &format!("../../tests/binaries/{name}");
612 let dd = TempDir::with_prefix("cargo-lambda-").expect("failed to create temp dir");
613 let archive =
614 zip_binary(bp, dd.path(), &data, None).expect("failed to create binary archive");
615
616 assert_eq!(arch, archive.architecture);
617
618 let arch_path = dd.path().join(format!("{name}.zip"));
619 assert_eq!(arch_path, archive.path);
620
621 let file = File::open(arch_path).expect("failed to open zip file");
622 let mut zip = ZipArchive::new(file).expect("failed to open zip archive");
623
624 zip.by_name(name)
625 .unwrap_or_else(|_| panic!("failed to find {name} in zip archive"));
626 }
627
628 #[rstest]
629 #[case("binary-x86-64", "x86_64")]
630 #[case("binary-arm64", "arm64")]
631 fn test_zip_funcion_with_files(#[case] name: &str, #[case] arch: &str) {
632 let data = BinaryData::new(name, false, false);
633
634 let bp = &format!("../../tests/binaries/{name}");
635 let extra = vec!["Cargo.toml".into()];
636 let dd = TempDir::with_prefix("cargo-lambda-").expect("failed to create temp dir");
637 let archive =
638 zip_binary(bp, dd.path(), &data, Some(extra)).expect("failed to create binary archive");
639
640 assert_eq!(arch, archive.architecture);
641
642 let arch_path = dd.path().join("bootstrap.zip");
643 assert_eq!(arch_path, archive.path);
644
645 let file = File::open(arch_path).expect("failed to open zip file");
646 let mut zip = ZipArchive::new(file).expect("failed to open zip archive");
647
648 zip.by_name("bootstrap")
649 .expect("failed to find bootstrap in zip archive");
650
651 zip.by_name("Cargo.toml")
652 .expect("failed to find Cargo.toml in zip archive");
653 }
654
655 #[test]
656 fn test_consistent_hash() {
657 let data = BinaryData::new("binary-x86-64", false, false);
658
659 let bp = "../../tests/binaries/binary-x86-64";
660 let dd = TempDir::with_prefix("cargo-lambda-").expect("failed to create temp dir");
661
662 let archive1 =
663 zip_binary(bp, dd.path(), &data, None).expect("failed to create binary archive");
664
665 sleep(Duration::from_secs(2));
667
668 let archive2 =
669 zip_binary(bp, dd.path(), &data, None).expect("failed to create binary archive");
670
671 assert_eq!(archive1.sha256().unwrap(), archive2.sha256().unwrap());
672 }
673
674 #[test]
675 fn test_create_binary_archive_with_base_path() {
676 let data = BinaryData::new("binary-x86-64", false, false);
677
678 let bp = "../../tests/binaries/binary-x86-64";
679 let dd = TempDir::with_prefix("cargo-lambda-").expect("failed to create temp dir");
680 let bsp = dd.path().join("binary-x86-64");
681
682 create_dir_all(&bsp).expect("failed to create dir");
683 copy_without_replace(bp, bsp.join("bootstrap")).expect("failed to copy bootstrap file");
684
685 let archive = create_binary_archive(None, &Some(dd.path()), &data, None)
686 .expect("failed to create binary archive");
687
688 let arch_path = bsp.join("bootstrap.zip");
689 assert_eq!(arch_path, archive.path);
690
691 let file = File::open(arch_path).expect("failed to open zip file");
692 let mut zip = ZipArchive::new(file).expect("failed to open zip archive");
693
694 zip.by_name("bootstrap")
695 .expect("failed to find bootstrap in zip archive");
696 }
697
698 #[test]
699 fn test_create_binary_archive_from_target() {
700 let data = BinaryData::new("binary-x86-64", false, false);
701
702 let bp = "../../tests/binaries/binary-x86-64";
703 let metadata = load_metadata("Cargo.toml").unwrap();
704 let target_dir =
705 target_dir_from_metadata(&metadata).unwrap_or_else(|_| PathBuf::from("target"));
706
707 let bsp = target_dir.join("lambda").join("binary-x86-64");
708
709 create_dir_all(&bsp).expect("failed to create dir");
710 copy_without_replace(bp, bsp.join("bootstrap")).expect("failed to copy bootstrap file");
711
712 let base_dir: Option<&Path> = None;
713 let archive = create_binary_archive(Some(&metadata), &base_dir, &data, None)
714 .expect("failed to create binary archive");
715
716 let arch_path = bsp.join("bootstrap.zip");
717 assert_eq!(arch_path, archive.path);
718
719 let file = File::open(arch_path).expect("failed to open zip file");
720 let mut zip = ZipArchive::new(file).expect("failed to open zip archive");
721
722 zip.by_name("bootstrap")
723 .expect("failed to find bootstrap in zip archive");
724
725 remove_dir_all(&bsp).expect("failed to delete dir");
726 }
727
728 #[test]
729 fn test_zip_funcion_with_parent_directories() {
730 let data = BinaryData::new("binary-x86-64", false, false);
731
732 let bp = "../../tests/binaries/binary-x86-64".to_string();
733 #[cfg(unix)]
734 let extra = vec!["source:../../tests/fixtures/examples-package".into()];
735 #[cfg(windows)]
736 let extra = vec!["source:..\\..\\tests\\fixtures\\examples-package".into()];
737
738 let dd = TempDir::with_prefix("cargo-lambda-").expect("failed to create temp dir");
739 let archive =
740 zip_binary(bp, dd.path(), &data, Some(extra)).expect("failed to create binary archive");
741
742 let arch_path = dd.path().join("bootstrap.zip");
743 assert_eq!(arch_path, archive.path);
744
745 let file = File::open(arch_path).expect("failed to open zip file");
746 let mut zip = ZipArchive::new(file).expect("failed to open zip archive");
747
748 zip.by_name("bootstrap")
749 .expect("failed to find bootstrap in zip archive");
750
751 zip.by_name("source/Cargo.toml")
752 .expect("failed to find Cargo.toml in zip archive");
753
754 zip.by_name("source/Cargo.lock")
755 .expect("failed to find Cargo.lock in zip archive");
756
757 zip.by_name("source/src/main.rs")
758 .expect("failed to find source/src/main.rs in zip archive");
759
760 zip.by_name("source/examples/example-lambda.rs")
761 .expect("failed to find source/examples/example-lambda.rs in zip archive");
762 }
763
764 #[rstest]
765 #[case("bootstrap.zip", "bootstrap")]
766 #[case("test-extension.zip", "extensions/test-extension")]
767 fn test_extract_data_from_zipped_bootstrap(#[case] name: &str, #[case] binary_path: &str) {
768 let zip_path = format!("../../tests/binaries/{name}");
769 let zip_path = Path::new(&zip_path);
770 let (arch, binary_modified_at) =
771 extract_data_from_zipped_binary(zip_path, binary_path).unwrap();
772
773 assert_eq!("x86_64", arch);
774 assertables::assert_some!(binary_modified_at.0);
775 }
776}