| 385 |
385 |
|
/// The image this box wants, or an error naming what the level requires.
|
| 386 |
386 |
|
fn image(&self, name: &str) -> Result<&str> {
|
| 387 |
387 |
|
self.image.as_deref().with_context(|| {
|
| 388 |
|
- |
format!("box `{name}` is {} and needs an `image`", self.level.label())
|
|
388 |
+ |
format!(
|
|
389 |
+ |
"box `{name}` is {} and needs an `image`",
|
|
390 |
+ |
self.level.label()
|
|
391 |
+ |
)
|
| 389 |
392 |
|
})
|
| 390 |
393 |
|
}
|
| 391 |
394 |
|
|
| 403 |
406 |
|
|
| 404 |
407 |
|
/// The app id this box wants, or an error naming what the level requires.
|
| 405 |
408 |
|
fn app(&self, name: &str) -> Result<&str> {
|
| 406 |
|
- |
self.app.as_deref().with_context(|| {
|
| 407 |
|
- |
format!("box `{name}` is {} and needs an `app`", self.level.label())
|
| 408 |
|
- |
})
|
|
409 |
+ |
self.app
|
|
410 |
+ |
.as_deref()
|
|
411 |
+ |
.with_context(|| format!("box `{name}` is {} and needs an `app`", self.level.label()))
|
| 409 |
412 |
|
}
|
| 410 |
413 |
|
}
|
| 411 |
414 |
|
|
| 543 |
546 |
|
let image = spec.image(name)?;
|
| 544 |
547 |
|
match spec.level {
|
| 545 |
548 |
|
Level::Host => Ok(Invocation::new("distrobox").args([
|
| 546 |
|
- |
"create",
|
| 547 |
|
- |
"--name",
|
| 548 |
|
- |
name,
|
| 549 |
|
- |
"--image",
|
| 550 |
|
- |
image,
|
|
549 |
+ |
"create", "--name", name, "--image", image,
|
| 551 |
550 |
|
// Non-interactive: distrobox otherwise prompts before pulling,
|
| 552 |
551 |
|
// and the console has handed it no terminal to prompt on.
|
| 553 |
552 |
|
"--yes",
|
| 565 |
564 |
|
// Named rather than a host path: the point of the level is that
|
| 566 |
565 |
|
// the box does not reach into the host filesystem except where
|
| 567 |
566 |
|
// it was told to.
|
| 568 |
|
- |
invocation = invocation
|
| 569 |
|
- |
.args(["--volume", &format!("{}:{WORKSPACE_HOME}", home_volume(name))]);
|
|
567 |
+ |
invocation = invocation.args([
|
|
568 |
+ |
"--volume",
|
|
569 |
+ |
&format!("{}:{WORKSPACE_HOME}", home_volume(name)),
|
|
570 |
+ |
]);
|
| 570 |
571 |
|
for mount in &spec.mounts {
|
| 571 |
572 |
|
invocation = invocation.args(["--volume", &bind(mount)?]);
|
| 572 |
573 |
|
}
|
| 632 |
633 |
|
}
|
| 633 |
634 |
|
|
| 634 |
635 |
|
fn start(&self, boxed: &Box) -> Option<Invocation> {
|
| 635 |
|
- |
(!boxed.state.is_running())
|
| 636 |
|
- |
.then(|| Invocation::new("podman").args(["start", &boxed.name]))
|
|
636 |
+ |
(!boxed.state.is_running()).then(|| Invocation::new("podman").args(["start", &boxed.name]))
|
| 637 |
637 |
|
}
|
| 638 |
638 |
|
|
| 639 |
639 |
|
fn stop(&self, boxed: &Box) -> Option<Invocation> {
|
| 895 |
895 |
|
// A sandboxed box contains exactly one app, so the app is
|
| 896 |
896 |
|
// the box. Its human name is the identity a user recognizes;
|
| 897 |
897 |
|
// the app id is the source that identifies it to flatpak.
|
| 898 |
|
- |
name: if name.is_empty() { app.to_string() } else { name.to_string() },
|
|
898 |
+ |
name: if name.is_empty() {
|
|
899 |
+ |
app.to_string()
|
|
900 |
+ |
} else {
|
|
901 |
+ |
name.to_string()
|
|
902 |
+ |
},
|
| 899 |
903 |
|
// Always known: flatpak implements exactly one level, so
|
| 900 |
904 |
|
// even an app installed outside Alloy is sandboxed.
|
| 901 |
905 |
|
level: Some(Level::Sandboxed),
|
| 984 |
988 |
|
rows.sort_by(|a, b| {
|
| 985 |
989 |
|
(a.boxed.origin() != Origin::Declared)
|
| 986 |
990 |
|
.cmp(&(b.boxed.origin() != Origin::Declared))
|
| 987 |
|
- |
.then_with(|| a.boxed.name.to_lowercase().cmp(&b.boxed.name.to_lowercase()))
|
|
991 |
+ |
.then_with(|| {
|
|
992 |
+ |
a.boxed
|
|
993 |
+ |
.name
|
|
994 |
+ |
.to_lowercase()
|
|
995 |
+ |
.cmp(&b.boxed.name.to_lowercase())
|
|
996 |
+ |
})
|
| 988 |
997 |
|
});
|
| 989 |
998 |
|
}
|
| 990 |
999 |
|
|
| 1302 |
1311 |
|
// fact that decides whether removing it is cheap or permanent.
|
| 1303 |
1312 |
|
let message = match boxed.origin() {
|
| 1304 |
1313 |
|
Origin::Declared => format!("Remove {title}? It can be rebuilt from the spec."),
|
| 1305 |
|
- |
Origin::AdHoc => format!("Remove {title}? It is not in the spec and cannot be rebuilt."),
|
|
1314 |
+ |
Origin::AdHoc => {
|
|
1315 |
+ |
format!("Remove {title}? It is not in the spec and cannot be rebuilt.")
|
|
1316 |
+ |
}
|
| 1306 |
1317 |
|
};
|
| 1307 |
1318 |
|
self.pending = Some(PendingRemove {
|
| 1308 |
1319 |
|
backend: index,
|
| 1684 |
1695 |
|
#[test]
|
| 1685 |
1696 |
|
fn a_sandboxed_row_shows_the_name_and_carries_the_app_id() {
|
| 1686 |
1697 |
|
let boxes = Flatpak.parse(FLATPAK, &Spec::default()).unwrap();
|
| 1687 |
|
- |
let chromium = boxes.iter().find(|b| b.source == "org.chromium.Chromium").unwrap();
|
|
1698 |
+ |
let chromium = boxes
|
|
1699 |
+ |
.iter()
|
|
1700 |
+ |
.find(|b| b.source == "org.chromium.Chromium")
|
|
1701 |
+ |
.unwrap();
|
| 1688 |
1702 |
|
assert_eq!(chromium.name, "Chromium Web Browser");
|
| 1689 |
1703 |
|
}
|
| 1690 |
1704 |
|
|
| 1704 |
1718 |
|
.unwrap();
|
| 1705 |
1719 |
|
|
| 1706 |
1720 |
|
let boxes = Flatpak.parse(FLATPAK, &spec).unwrap();
|
| 1707 |
|
- |
let chromium = boxes.iter().find(|b| b.source == "org.chromium.Chromium").unwrap();
|
|
1721 |
+ |
let chromium = boxes
|
|
1722 |
+ |
.iter()
|
|
1723 |
+ |
.find(|b| b.source == "org.chromium.Chromium")
|
|
1724 |
+ |
.unwrap();
|
| 1708 |
1725 |
|
assert_eq!(chromium.origin(), Origin::Declared);
|
| 1709 |
1726 |
|
assert_eq!(
|
| 1710 |
1727 |
|
chromium.declared.as_deref(),
|
| 1712 |
1729 |
|
"the row carries the spec's name for it, not the app id"
|
| 1713 |
1730 |
|
);
|
| 1714 |
1731 |
|
|
| 1715 |
|
- |
let tasks = boxes.iter().find(|b| b.source == "dev.edfloreshz.Tasks").unwrap();
|
|
1732 |
+ |
let tasks = boxes
|
|
1733 |
+ |
.iter()
|
|
1734 |
+ |
.find(|b| b.source == "dev.edfloreshz.Tasks")
|
|
1735 |
+ |
.unwrap();
|
| 1716 |
1736 |
|
assert_eq!(tasks.origin(), Origin::AdHoc);
|
| 1717 |
1737 |
|
}
|
| 1718 |
1738 |
|
|
| 1719 |
1739 |
|
// An app with no human name still needs an identifiable row.
|
| 1720 |
1740 |
|
#[test]
|
| 1721 |
1741 |
|
fn a_flatpak_row_with_a_missing_name_falls_back_to_the_app_id() {
|
| 1722 |
|
- |
let boxes = Flatpak.parse("org.example.Thing\t\tflathub\n", &Spec::default()).unwrap();
|
|
1742 |
+ |
let boxes = Flatpak
|
|
1743 |
+ |
.parse("org.example.Thing\t\tflathub\n", &Spec::default())
|
|
1744 |
+ |
.unwrap();
|
| 1723 |
1745 |
|
assert_eq!(boxes[0].name, "org.example.Thing");
|
| 1724 |
1746 |
|
}
|
| 1725 |
1747 |
|
|
| 1742 |
1764 |
|
let stopped = &boxes[1];
|
| 1743 |
1765 |
|
|
| 1744 |
1766 |
|
assert_eq!(Podman.list().display(), "podman ps --format json --all");
|
| 1745 |
|
- |
assert_eq!(Podman.stop(running).unwrap().display(), "podman stop scratch");
|
| 1746 |
|
- |
assert!(Podman.start(running).is_none(), "a running box has nothing to start");
|
| 1747 |
|
- |
assert_eq!(Podman.start(stopped).unwrap().display(), "podman start stray");
|
| 1748 |
|
- |
assert!(Podman.stop(stopped).is_none(), "a stopped box has nothing to stop");
|
| 1749 |
|
- |
assert_eq!(Podman.remove(running).display(), "podman rm --force scratch");
|
|
1767 |
+ |
assert_eq!(
|
|
1768 |
+ |
Podman.stop(running).unwrap().display(),
|
|
1769 |
+ |
"podman stop scratch"
|
|
1770 |
+ |
);
|
|
1771 |
+ |
assert!(
|
|
1772 |
+ |
Podman.start(running).is_none(),
|
|
1773 |
+ |
"a running box has nothing to start"
|
|
1774 |
+ |
);
|
|
1775 |
+ |
assert_eq!(
|
|
1776 |
+ |
Podman.start(stopped).unwrap().display(),
|
|
1777 |
+ |
"podman start stray"
|
|
1778 |
+ |
);
|
|
1779 |
+ |
assert!(
|
|
1780 |
+ |
Podman.stop(stopped).is_none(),
|
|
1781 |
+ |
"a stopped box has nothing to stop"
|
|
1782 |
+ |
);
|
|
1783 |
+ |
assert_eq!(
|
|
1784 |
+ |
Podman.remove(running).display(),
|
|
1785 |
+ |
"podman rm --force scratch"
|
|
1786 |
+ |
);
|
| 1750 |
1787 |
|
}
|
| 1751 |
1788 |
|
|
| 1752 |
1789 |
|
// Enter has to match how the box was made, or pressing it on a bare podman
|
| 1759 |
1796 |
|
let adhoc = boxes.iter().find(|b| b.name == "stray").unwrap();
|
| 1760 |
1797 |
|
|
| 1761 |
1798 |
|
assert_eq!(Podman.enter(declared).display(), "distrobox enter scratch");
|
| 1762 |
|
- |
assert_eq!(Podman.enter(adhoc).display(), "podman exec -it stray /bin/sh");
|
|
1799 |
+ |
assert_eq!(
|
|
1800 |
+ |
Podman.enter(adhoc).display(),
|
|
1801 |
+ |
"podman exec -it stray /bin/sh"
|
|
1802 |
+ |
);
|
| 1763 |
1803 |
|
}
|
| 1764 |
1804 |
|
|
| 1765 |
1805 |
|
// The branch is on level, not origin. A declared `workspace` box was made by
|
| 1771 |
1811 |
|
let boxes = Podman.parse(PODMAN, &spec).unwrap();
|
| 1772 |
1812 |
|
let workspace = boxes.iter().find(|b| b.name == "scratch").unwrap();
|
| 1773 |
1813 |
|
|
| 1774 |
|
- |
assert_eq!(workspace.origin(), Origin::Declared, "declared, and still not distrobox's");
|
|
1814 |
+ |
assert_eq!(
|
|
1815 |
+ |
workspace.origin(),
|
|
1816 |
+ |
Origin::Declared,
|
|
1817 |
+ |
"declared, and still not distrobox's"
|
|
1818 |
+ |
);
|
| 1775 |
1819 |
|
assert_eq!(
|
| 1776 |
1820 |
|
Podman.enter(workspace).display(),
|
| 1777 |
1821 |
|
"podman exec -it scratch /bin/sh"
|
| 1781 |
1825 |
|
#[test]
|
| 1782 |
1826 |
|
fn flatpak_commands_address_the_app_id() {
|
| 1783 |
1827 |
|
let boxes = Flatpak.parse(FLATPAK, &Spec::default()).unwrap();
|
| 1784 |
|
- |
let chromium = boxes.iter().find(|b| b.source == "org.chromium.Chromium").unwrap();
|
|
1828 |
+ |
let chromium = boxes
|
|
1829 |
+ |
.iter()
|
|
1830 |
+ |
.find(|b| b.source == "org.chromium.Chromium")
|
|
1831 |
+ |
.unwrap();
|
| 1785 |
1832 |
|
|
| 1786 |
1833 |
|
assert_eq!(
|
| 1787 |
1834 |
|
Flatpak.list().display(),
|
| 1861 |
1908 |
|
assert_eq!(
|
| 1862 |
1909 |
|
lines,
|
| 1863 |
1910 |
|
vec![
|
| 1864 |
|
- |
format!("distrobox enter dev -- distrobox-export --bin /usr/bin/rg --export-path {dir}"),
|
| 1865 |
|
- |
format!("distrobox enter dev -- distrobox-export --bin /usr/bin/fd --export-path {dir}"),
|
|
1911 |
+ |
format!(
|
|
1912 |
+ |
"distrobox enter dev -- distrobox-export --bin /usr/bin/rg --export-path {dir}"
|
|
1913 |
+ |
),
|
|
1914 |
+ |
format!(
|
|
1915 |
+ |
"distrobox enter dev -- distrobox-export --bin /usr/bin/fd --export-path {dir}"
|
|
1916 |
+ |
),
|
| 1866 |
1917 |
|
]
|
| 1867 |
1918 |
|
);
|
| 1868 |
1919 |
|
}
|
| 1878 |
1929 |
|
let [Effect::Write { path, mode, .. }] = effects.as_slice() else {
|
| 1879 |
1930 |
|
panic!("workspace exports are writes, not commands");
|
| 1880 |
1931 |
|
};
|
| 1881 |
|
- |
assert!(path.ends_with(".local/bin/cargo"), "landed at {}", path.display());
|
| 1882 |
|
- |
assert_eq!(*mode, 0o755, "a wrapper the shell will not run is not on the PATH");
|
|
1932 |
+ |
assert!(
|
|
1933 |
+ |
path.ends_with(".local/bin/cargo"),
|
|
1934 |
+ |
"landed at {}",
|
|
1935 |
+ |
path.display()
|
|
1936 |
+ |
);
|
|
1937 |
+ |
assert_eq!(
|
|
1938 |
+ |
*mode, 0o755,
|
|
1939 |
+ |
"a wrapper the shell will not run is not on the PATH"
|
|
1940 |
+ |
);
|
| 1883 |
1941 |
|
}
|
| 1884 |
1942 |
|
|
| 1885 |
1943 |
|
// The wrapper is what makes the level's promise hold once the binary is on
|
| 1928 |
1986 |
|
fn a_box_with_no_mounts_gets_a_fixed_working_directory() {
|
| 1929 |
1987 |
|
let script = wrapper_of(&export_spec(), "solo");
|
| 1930 |
1988 |
|
|
| 1931 |
|
- |
assert!(!script.contains("case"), "nothing to match against:\n{script}");
|
|
1989 |
+ |
assert!(
|
|
1990 |
+ |
!script.contains("case"),
|
|
1991 |
+ |
"nothing to match against:\n{script}"
|
|
1992 |
+ |
);
|
| 1932 |
1993 |
|
assert!(script.contains("workdir='/root'"), "{script}");
|
| 1933 |
1994 |
|
}
|
| 1934 |
1995 |
|
|
| 1946 |
2007 |
|
#[test]
|
| 1947 |
2008 |
|
fn flatpak_refuses_to_export_and_says_the_app_is_already_there() {
|
| 1948 |
2009 |
|
let spec = export_spec();
|
| 1949 |
|
- |
let err = exports(&Flatpak, &spec, "chromium").unwrap_err().to_string();
|
|
2010 |
+ |
let err = exports(&Flatpak, &spec, "chromium")
|
|
2011 |
+ |
.unwrap_err()
|
|
2012 |
+ |
.to_string();
|
| 1950 |
2013 |
|
assert!(err.contains("already exports"), "{err}");
|
| 1951 |
2014 |
|
}
|
| 1952 |
2015 |
|
|
| 2054 |
2117 |
|
|
| 2055 |
2118 |
|
let (_, workspace) = spec.resolve("nowhere").unwrap();
|
| 2056 |
2119 |
|
let err = Podman.create("nowhere", workspace).unwrap_err().to_string();
|
| 2057 |
|
- |
assert!(err.contains("nowhere") && err.contains("`image`"), "got: {err}");
|
|
2120 |
+ |
assert!(
|
|
2121 |
+ |
err.contains("nowhere") && err.contains("`image`"),
|
|
2122 |
+ |
"got: {err}"
|
|
2123 |
+ |
);
|
| 2058 |
2124 |
|
|
| 2059 |
2125 |
|
let (_, sandboxed) = spec.resolve("nothing").unwrap();
|
| 2060 |
|
- |
let err = Flatpak.create("nothing", sandboxed).unwrap_err().to_string();
|
| 2061 |
|
- |
assert!(err.contains("nothing") && err.contains("`app`"), "got: {err}");
|
|
2126 |
+ |
let err = Flatpak
|
|
2127 |
+ |
.create("nothing", sandboxed)
|
|
2128 |
+ |
.unwrap_err()
|
|
2129 |
+ |
.to_string();
|
|
2130 |
+ |
assert!(
|
|
2131 |
+ |
err.contains("nothing") && err.contains("`app`"),
|
|
2132 |
+ |
"got: {err}"
|
|
2133 |
+ |
);
|
| 2062 |
2134 |
|
}
|
| 2063 |
2135 |
|
|
| 2064 |
2136 |
|
// A malformed entry must not cost the other boxes their declared marker, so
|
| 2102 |
2174 |
|
#[test]
|
| 2103 |
2175 |
|
fn a_relative_mount_is_rejected_with_the_spec_in_the_message() {
|
| 2104 |
2176 |
|
let err = bind("code/thing").unwrap_err().to_string();
|
| 2105 |
|
- |
assert!(err.contains("code/thing") && err.contains("absolute"), "got: {err}");
|
|
2177 |
+ |
assert!(
|
|
2178 |
+ |
err.contains("code/thing") && err.contains("absolute"),
|
|
2179 |
+ |
"got: {err}"
|
|
2180 |
+ |
);
|
| 2106 |
2181 |
|
}
|
| 2107 |
2182 |
|
|
| 2108 |
2183 |
|
// ---- the dial routes one way ----
|
| 2151 |
2226 |
|
// The source fields creating reads, and the level that picks between
|
| 2152 |
2227 |
|
// them.
|
| 2153 |
2228 |
|
let (_, dev) = spec.resolve("dev").unwrap();
|
| 2154 |
|
- |
assert_eq!(dev.image.as_deref(), Some("registry.fedoraproject.org/fedora-toolbox:43"));
|
|
2229 |
+ |
assert_eq!(
|
|
2230 |
+ |
dev.image.as_deref(),
|
|
2231 |
+ |
Some("registry.fedoraproject.org/fedora-toolbox:43")
|
|
2232 |
+ |
);
|
| 2155 |
2233 |
|
let (_, scratch) = spec.resolve("scratch").unwrap();
|
| 2156 |
2234 |
|
assert_eq!(scratch.mounts, ["~/code/thing"]);
|
| 2157 |
2235 |
|
let (_, chromium) = spec.resolve("chromium").unwrap();
|
| 2302 |
2380 |
|
let (mut view, mut log) = fixture_view(Tab::System, sample_boxes());
|
| 2303 |
2381 |
|
view.handle(KeyEvent::from(KeyCode::Char('j')), &mut log);
|
| 2304 |
2382 |
|
assert_eq!(view.cursor.selected(), Some(0), "the cursor did not move");
|
| 2305 |
|
- |
assert!(matches!(
|
| 2306 |
|
- |
view.handle(KeyEvent::from(KeyCode::Char('x')), &mut log),
|
| 2307 |
|
- |
Flow::Continue
|
| 2308 |
|
- |
), "no confirm is raised from a tab with nothing to remove");
|
|
2383 |
+ |
assert!(
|
|
2384 |
+ |
matches!(
|
|
2385 |
+ |
view.handle(KeyEvent::from(KeyCode::Char('x')), &mut log),
|
|
2386 |
+ |
Flow::Continue
|
|
2387 |
+ |
),
|
|
2388 |
+ |
"no confirm is raised from a tab with nothing to remove"
|
|
2389 |
+ |
);
|
| 2309 |
2390 |
|
assert!(view.pending.is_none());
|
| 2310 |
2391 |
|
}
|
| 2311 |
2392 |
|
|
| 2317 |
2398 |
|
assert!(message.contains("rpm-ostree"), "got: {message}");
|
| 2318 |
2399 |
|
|
| 2319 |
2400 |
|
let (view, _log) = fixture_view(Tab::Boxes, Vec::new());
|
| 2320 |
|
- |
assert_eq!(view.status(), None, "the live tab has nothing to warn about");
|
|
2401 |
+ |
assert_eq!(
|
|
2402 |
+ |
view.status(),
|
|
2403 |
+ |
None,
|
|
2404 |
+ |
"the live tab has nothing to warn about"
|
|
2405 |
+ |
);
|
| 2321 |
2406 |
|
}
|
| 2322 |
2407 |
|
|
| 2323 |
2408 |
|
// Removing is the console's first destructive action. It must raise a
|
| 2331 |
2416 |
|
};
|
| 2332 |
2417 |
|
assert_eq!(confirm.title, "dev");
|
| 2333 |
2418 |
|
assert_eq!(confirm.severity, Severity::Error);
|
| 2334 |
|
- |
assert!(view.pending.is_some(), "the view kept what it was about to do");
|
|
2419 |
+ |
assert!(
|
|
2420 |
+ |
view.pending.is_some(),
|
|
2421 |
+ |
"the view kept what it was about to do"
|
|
2422 |
+ |
);
|
| 2335 |
2423 |
|
}
|
| 2336 |
2424 |
|
|
| 2337 |
2425 |
|
// The prompt has to carry the fact that decides the answer: an ad-hoc box is
|
| 2344 |
2432 |
|
else {
|
| 2345 |
2433 |
|
panic!("expected a confirm");
|
| 2346 |
2434 |
|
};
|
| 2347 |
|
- |
assert!(declared.message.contains("can be rebuilt"), "got: {}", declared.message);
|
|
2435 |
+ |
assert!(
|
|
2436 |
+ |
declared.message.contains("can be rebuilt"),
|
|
2437 |
+ |
"got: {}",
|
|
2438 |
+ |
declared.message
|
|
2439 |
+ |
);
|
| 2348 |
2440 |
|
|
| 2349 |
2441 |
|
view.cancelled();
|
| 2350 |
2442 |
|
view.cursor.next();
|
| 2351 |
2443 |
|
let Flow::Confirm(adhoc) = view.handle(KeyEvent::from(KeyCode::Char('x')), &mut log) else {
|
| 2352 |
2444 |
|
panic!("expected a confirm");
|
| 2353 |
2445 |
|
};
|
| 2354 |
|
- |
assert!(adhoc.message.contains("cannot be rebuilt"), "got: {}", adhoc.message);
|
|
2446 |
+ |
assert!(
|
|
2447 |
+ |
adhoc.message.contains("cannot be rebuilt"),
|
|
2448 |
+ |
"got: {}",
|
|
2449 |
+ |
adhoc.message
|
|
2450 |
+ |
);
|
| 2355 |
2451 |
|
}
|
| 2356 |
2452 |
|
|
| 2357 |
2453 |
|
// Declining must leave nothing armed, or the next confirm the view raises
|
| 2456 |
2552 |
|
let rows = view.absent_rows(&BTreeSet::from(["dev"]), &[0]);
|
| 2457 |
2553 |
|
|
| 2458 |
2554 |
|
let names: Vec<&str> = rows.iter().map(|row| row.boxed.name.as_str()).collect();
|
| 2459 |
|
- |
assert_eq!(names, ["chromium", "scratch"], "dev is present, so it is not missing");
|
|
2555 |
+ |
assert_eq!(
|
|
2556 |
+ |
names,
|
|
2557 |
+ |
["chromium", "scratch"],
|
|
2558 |
+ |
"dev is present, so it is not missing"
|
|
2559 |
+ |
);
|
| 2460 |
2560 |
|
}
|
| 2461 |
2561 |
|
|
| 2462 |
2562 |
|
// A backend that failed to list has said nothing about whether its boxes
|
| 2469 |
2569 |
|
|
| 2470 |
2570 |
|
let names: Vec<&str> = rows.iter().map(|row| row.boxed.name.as_str()).collect();
|
| 2471 |
2571 |
|
assert_eq!(
|
| 2472 |
|
- |
names, ["chromium"],
|
|
2572 |
+ |
names,
|
|
2573 |
+ |
["chromium"],
|
| 2473 |
2574 |
|
"podman said nothing, so its boxes are unknown rather than missing"
|
| 2474 |
2575 |
|
);
|
| 2475 |
2576 |
|
}
|
| 2625 |
2726 |
|
let mut log = CommandLog::new();
|
| 2626 |
2727 |
|
|
| 2627 |
2728 |
|
// Clean up anything a previous run left behind.
|
| 2628 |
|
- |
let _ = Invocation::new("podman").args(["rm", "--force", name]).probe();
|
|
2729 |
+ |
let _ = Invocation::new("podman")
|
|
2730 |
+ |
.args(["rm", "--force", name])
|
|
2731 |
+ |
.probe();
|
| 2629 |
2732 |
|
let _ = Invocation::new("podman")
|
| 2630 |
2733 |
|
.args(["volume", "rm", "--force", &home_volume(name)])
|
| 2631 |
2734 |
|
.probe();
|
| 2632 |
2735 |
|
|
| 2633 |
2736 |
|
Podman.create(name, entry).unwrap().run(&mut log).unwrap();
|
| 2634 |
|
- |
Invocation::new("podman").args(["start", name]).run(&mut log).unwrap();
|
|
2737 |
+ |
Invocation::new("podman")
|
|
2738 |
+ |
.args(["start", name])
|
|
2739 |
+ |
.run(&mut log)
|
|
2740 |
+ |
.unwrap();
|
| 2635 |
2741 |
|
|
| 2636 |
2742 |
|
let reaches = |command: &str| {
|
| 2637 |
2743 |
|
Invocation::new("podman")
|
| 2641 |
2747 |
|
};
|
| 2642 |
2748 |
|
|
| 2643 |
2749 |
|
let home = std::env::var("HOME").unwrap();
|
| 2644 |
|
- |
assert!(!reaches(&format!("ls {home}")), "the host home is not reachable");
|
| 2645 |
|
- |
assert!(!reaches("ls /run/user"), "the host runtime dir, and its D-Bus socket, is absent");
|
| 2646 |
|
- |
assert!(reaches(&format!("ls {}", mount.display())), "the named mount is there");
|
|
2750 |
+ |
assert!(
|
|
2751 |
+ |
!reaches(&format!("ls {home}")),
|
|
2752 |
+ |
"the host home is not reachable"
|
|
2753 |
+ |
);
|
|
2754 |
+ |
assert!(
|
|
2755 |
+ |
!reaches("ls /run/user"),
|
|
2756 |
+ |
"the host runtime dir, and its D-Bus socket, is absent"
|
|
2757 |
+ |
);
|
|
2758 |
+ |
assert!(
|
|
2759 |
+ |
reaches(&format!("ls {}", mount.display())),
|
|
2760 |
+ |
"the named mount is there"
|
|
2761 |
+ |
);
|
| 2647 |
2762 |
|
assert!(reaches("getent hosts example.com"), "network stays on");
|
| 2648 |
2763 |
|
|
| 2649 |
2764 |
|
Podman
|
| 2694 |
2809 |
|
let (_, entry) = spec.resolve(name).unwrap();
|
| 2695 |
2810 |
|
let mut log = CommandLog::new();
|
| 2696 |
2811 |
|
let cleanup = || {
|
| 2697 |
|
- |
let _ = Invocation::new("podman").args(["rm", "--force", name]).probe();
|
|
2812 |
+ |
let _ = Invocation::new("podman")
|
|
2813 |
+ |
.args(["rm", "--force", name])
|
|
2814 |
+ |
.probe();
|
| 2698 |
2815 |
|
let _ = Invocation::new("podman")
|
| 2699 |
2816 |
|
.args(["volume", "rm", "--force", &home_volume(name)])
|
| 2700 |
2817 |
|
.probe();
|
| 2711 |
2828 |
|
std::fs::create_dir_all(&bin).unwrap();
|
| 2712 |
2829 |
|
let mut wrappers = std::collections::BTreeMap::new();
|
| 2713 |
2830 |
|
for effect in Podman.export(name, entry).unwrap() {
|
| 2714 |
|
- |
let Effect::Write { path, contents, mode } = effect else {
|
|
2831 |
+ |
let Effect::Write {
|
|
2832 |
+ |
path,
|
|
2833 |
+ |
contents,
|
|
2834 |
+ |
mode,
|
|
2835 |
+ |
} = effect
|
|
2836 |
+ |
else {
|
| 2715 |
2837 |
|
panic!("workspace exports are writes");
|
| 2716 |
2838 |
|
};
|
| 2717 |
2839 |
|
let command = path.file_name().unwrap().to_string_lossy().to_string();
|
| 2759 |
2881 |
|
.unwrap();
|
| 2760 |
2882 |
|
std::io::Write::write_all(piped.stdin.as_mut().unwrap(), b"through the pipe\n").unwrap();
|
| 2761 |
2883 |
|
let piped = piped.wait_with_output().unwrap();
|
| 2762 |
|
- |
assert!(piped.status.success(), "{}", String::from_utf8_lossy(&piped.stderr));
|
| 2763 |
|
- |
assert_eq!(String::from_utf8_lossy(&piped.stdout).trim(), "through the pipe");
|
|
2884 |
+ |
assert!(
|
|
2885 |
+ |
piped.status.success(),
|
|
2886 |
+ |
"{}",
|
|
2887 |
+ |
String::from_utf8_lossy(&piped.stderr)
|
|
2888 |
+ |
);
|
|
2889 |
+ |
assert_eq!(
|
|
2890 |
+ |
String::from_utf8_lossy(&piped.stdout).trim(),
|
|
2891 |
+ |
"through the pipe"
|
|
2892 |
+ |
);
|
| 2764 |
2893 |
|
|
| 2765 |
2894 |
|
cleanup();
|
| 2766 |
2895 |
|
std::fs::remove_dir_all(&mount).ok();
|
| 2805 |
2934 |
|
let mut log = CommandLog::new();
|
| 2806 |
2935 |
|
let exported = export_dir().unwrap().join("dnf");
|
| 2807 |
2936 |
|
let cleanup = || {
|
| 2808 |
|
- |
let _ = Invocation::new("distrobox").args(["rm", "--force", name]).probe();
|
| 2809 |
|
- |
let _ = Invocation::new("podman").args(["rm", "--force", name]).probe();
|
|
2937 |
+ |
let _ = Invocation::new("distrobox")
|
|
2938 |
+ |
.args(["rm", "--force", name])
|
|
2939 |
+ |
.probe();
|
|
2940 |
+ |
let _ = Invocation::new("podman")
|
|
2941 |
+ |
.args(["rm", "--force", name])
|
|
2942 |
+ |
.probe();
|
| 2810 |
2943 |
|
std::fs::remove_file(&exported).ok();
|
| 2811 |
2944 |
|
};
|
| 2812 |
2945 |
|
cleanup();
|
| 2818 |
2951 |
|
let Effect::Run(_) = &effect else {
|
| 2819 |
2952 |
|
panic!("host exports go through distrobox-export, not a written file");
|
| 2820 |
2953 |
|
};
|
| 2821 |
|
- |
effect.apply(&mut log).expect("distrobox accepts the argv Alloy builds");
|
|
2954 |
+ |
effect
|
|
2955 |
+ |
.apply(&mut log)
|
|
2956 |
+ |
.expect("distrobox accepts the argv Alloy builds");
|
| 2822 |
2957 |
|
}
|
| 2823 |
2958 |
|
|
| 2824 |
2959 |
|
assert!(
|
| 2832 |
2967 |
|
);
|
| 2833 |
2968 |
|
|
| 2834 |
2969 |
|
cleanup();
|
| 2835 |
|
- |
assert!(!exported.exists(), "and the test leaves nothing on the PATH");
|
|
2970 |
+ |
assert!(
|
|
2971 |
+ |
!exported.exists(),
|
|
2972 |
+ |
"and the test leaves nothing on the PATH"
|
|
2973 |
+ |
);
|
| 2836 |
2974 |
|
}
|
| 2837 |
2975 |
|
|
| 2838 |
2976 |
|
/// Parse this machine's real podman and flatpak output.
|
| 2847 |
2985 |
|
let spec = Spec::load();
|
| 2848 |
2986 |
|
|
| 2849 |
2987 |
|
for backend in detect() {
|