Browse Source

allow all dpkg selections to be set via apt-mark and libapt

As we have support for 'hold', we need support for undoing a hold which
in effect means that we implemented most other states as well, just that
they weren't exposed in the interface directly so far.
tags/debian/1.1_exp15
David Kalnischkies 6 years ago
parent
commit
64e3414e00
7 changed files with 297 additions and 130 deletions
  1. +61
    -27
      apt-pkg/statechanges.cc
  2. +12
    -6
      apt-pkg/statechanges.h
  3. +20
    -8
      apt-private/private-cmndline.cc
  4. +65
    -37
      cmdline/apt-mark.cc
  5. +64
    -46
      doc/apt-mark.8.xml
  6. +35
    -6
      doc/apt-verbatim.ent
  7. +40
    -0
      test/integration/test-apt-mark

+ 61
- 27
apt-pkg/statechanges.cc View File

@@ -14,46 +14,62 @@ class StateChanges::Private
{
public:
APT::VersionVector hold;
APT::VersionVector unhold;
APT::VersionVector install;
APT::VersionVector deinstall;
APT::VersionVector purge;
APT::VersionVector error;
};

void StateChanges::Hold(pkgCache::VerIterator const &Ver)
{
d->hold.push_back(Ver);
}
APT::VersionVector& StateChanges::Hold()
{
return d->hold;
}
void StateChanges::Unhold(pkgCache::VerIterator const &Ver)
{
d->install.push_back(Ver);
}
APT::VersionVector& StateChanges::Unhold()
{
return d->install;
#define APT_GETTERSETTER(Name, Container) \
void StateChanges::Name(pkgCache::VerIterator const &Ver) \
{ \
Container.push_back(Ver); \
}\
APT::VersionVector& StateChanges::Name() \
{ \
return Container; \
}
APT_GETTERSETTER(Hold, d->hold)
APT_GETTERSETTER(Unhold, d->unhold)
APT_GETTERSETTER(Install, d->install)
APT_GETTERSETTER(Remove, d->deinstall)
APT_GETTERSETTER(Purge, d->purge)
#undef APT_GETTERSETTER
APT::VersionVector& StateChanges::Error()
{
return d->error;
}

void StateChanges::Discard()
void StateChanges::clear()
{
d->hold.clear();
d->unhold.clear();
d->install.clear();
d->deinstall.clear();
d->purge.clear();
d->error.clear();
}

bool StateChanges::empty() const
{
return d->hold.empty() &&
d->unhold.empty() &&
d->install.empty() &&
d->deinstall.empty() &&
d->purge.empty() &&
d->error.empty();
}

bool StateChanges::Save(bool const DiscardOutput)
{
d->error.clear();
if (d->hold.empty() && d->install.empty())
if (d->hold.empty() && d->unhold.empty() && d->install.empty() && d->deinstall.empty() && d->purge.empty())
return true;

std::vector<std::string> Args = debSystem::GetDpkgBaseCommand();
// ensure dpkg knows about the package so that it keeps the status we set
if (d->hold.empty() == false || d->install.empty() == false)
{
APT::VersionVector makeDpkgAvailable;
auto const notInstalled = [](pkgCache::VerIterator const &V) { return V.ParentPkg()->CurrentVer == 0; };
@@ -94,6 +110,24 @@ bool StateChanges::Save(bool const DiscardOutput)
else
fprintf(dpkg, "%s:%s %s\n", P.Name(), V.Arch(), state.c_str());
};
for (auto const &V: d->unhold)
{
if (V.ParentPkg()->CurrentVer != 0)
state = "install";
else
state = "deinstall";
dpkgName(V);
}
if (d->purge.empty() == false)
{
state = "purge";
std::for_each(d->purge.begin(), d->purge.end(), dpkgName);
}
if (d->deinstall.empty() == false)
{
state = "deinstall";
std::for_each(d->deinstall.begin(), d->deinstall.end(), dpkgName);
}
if (d->hold.empty() == false)
{
state = "hold";
@@ -108,16 +142,16 @@ bool StateChanges::Save(bool const DiscardOutput)

if (ExecWait(dpkgSelections, "dpkg --set-selections") == false)
{
if (d->hold.empty())
std::swap(d->install, d->error);
else if (d->install.empty())
std::swap(d->hold, d->error);
else
{
std::swap(d->hold, d->error);
std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error));
d->install.clear();
}
std::move(d->purge.begin(), d->purge.end(), std::back_inserter(d->error));
d->purge.clear();
std::move(d->deinstall.begin(), d->deinstall.end(), std::back_inserter(d->error));
d->deinstall.clear();
std::move(d->hold.begin(), d->hold.end(), std::back_inserter(d->error));
d->hold.clear();
std::move(d->unhold.begin(), d->unhold.end(), std::back_inserter(d->error));
d->unhold.clear();
std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error));
d->install.clear();
}
return d->error.empty();
}


+ 12
- 6
apt-pkg/statechanges.h View File

@@ -14,14 +14,20 @@ class APT_PUBLIC StateChanges
{
public:
// getter/setter for the different states
APT::VersionVector& Hold();
void Hold(pkgCache::VerIterator const &Ver);
APT::VersionVector& Unhold();
void Unhold(pkgCache::VerIterator const &Ver);
#define APT_GETTERSETTER(Name) \
APT::VersionVector& Name(); \
void Name(pkgCache::VerIterator const &Ver)
APT_GETTERSETTER(Hold);
APT_GETTERSETTER(Unhold);
APT_GETTERSETTER(Install);
APT_GETTERSETTER(Remove);
APT_GETTERSETTER(Purge);
APT::VersionVector& Error();
#undef APT_GETTERSETTER

// forgets all unsaved changes
void Discard();
// operate on all containers at once
void clear();
bool empty() const;

/** commit the staged changes to the database(s).
*


+ 20
- 8
apt-private/private-cmndline.cc View File

@@ -227,19 +227,31 @@ static bool addArgumentsAPTGet(std::vector<CommandLine::Args> &Args, char const
static bool addArgumentsAPTMark(std::vector<CommandLine::Args> &Args, char const * const Cmd)/*{{{*/
{
if (CmdMatches("auto", "manual", "hold", "unhold", "showauto",
"showmanual", "showhold", "showholds", "install",
"showmanual", "showhold", "showholds",
"markauto", "unmarkauto"))
{
addArg('f',"file","Dir::State::extended_states",CommandLine::HasArg);
}
else if (CmdMatches("install", "remove", "deinstall", "purge",
"showinstall", "showinstalls", "showremove", "showremoves",
"showdeinstall", "showdeinstalls", "showpurge", "showpurges"))
;
else
return false;

addArg('v',"verbose","APT::MarkAuto::Verbose",0);
addArg('s',"simulate","APT::Mark::Simulate",0);
addArg('s',"just-print","APT::Mark::Simulate",0);
addArg('s',"recon","APT::Mark::Simulate",0);
addArg('s',"dry-run","APT::Mark::Simulate",0);
addArg('s',"no-act","APT::Mark::Simulate",0);
addArg('f',"file","Dir::State::extended_states",CommandLine::HasArg);
if (CmdMatches("markauto", "unmarkauto"))
{
addArg('v',"verbose","APT::MarkAuto::Verbose",0);
}

if (strncmp(Cmd, "show", strlen("show")) != 0)
{
addArg('s',"simulate","APT::Mark::Simulate",0);
addArg('s',"just-print","APT::Mark::Simulate",0);
addArg('s',"recon","APT::Mark::Simulate",0);
addArg('s',"dry-run","APT::Mark::Simulate",0);
addArg('s',"no-act","APT::Mark::Simulate",0);
}

return true;
}


+ 65
- 37
cmdline/apt-mark.cc View File

@@ -167,8 +167,8 @@ static bool ShowAuto(CommandLine &CmdL)
return true;
}
/*}}}*/
/* DoHold - mark packages as hold by dpkg {{{*/
static bool DoHold(CommandLine &CmdL)
// DoSelection - wrapping around dpkg selections /*{{{*/
static bool DoSelection(CommandLine &CmdL)
{
pkgCacheFile CacheFile;
pkgCache *Cache = CacheFile.GetPkgCache();
@@ -179,29 +179,39 @@ static bool DoHold(CommandLine &CmdL)
if (pkgset.empty() == true)
return _error->Error(_("No packages found"));

bool const MarkHold = strcasecmp(CmdL.FileList[0],"hold") == 0;

auto const part = std::stable_partition(pkgset.begin(), pkgset.end(),
[](pkgCache::VerIterator const &V) { return V.ParentPkg()->SelectedState == pkgCache::State::Hold; });

auto const doneBegin = MarkHold ? pkgset.begin() : part;
auto const doneEnd = MarkHold ? part : pkgset.end();

std::for_each(doneBegin, doneEnd, [&MarkHold](pkgCache::VerIterator const &V) {
if (MarkHold == true)
ioprintf(c1out, _("%s was already set on hold.\n"), V.ParentPkg().FullName(true).c_str());
else
ioprintf(c1out, _("%s was already not hold.\n"), V.ParentPkg().FullName(true).c_str());
});

if (doneBegin == pkgset.begin() && doneEnd == pkgset.end())
return true;

auto const changeBegin = MarkHold ? part : pkgset.begin();
auto const changeEnd = MarkHold ? pkgset.end() : part;

APT::StateChanges marks;
std::move(changeBegin, changeEnd, std::back_inserter(MarkHold ? marks.Hold() : marks.Unhold()));
if (strcasecmp(CmdL.FileList[0], "hold") == 0 || strcasecmp(CmdL.FileList[0], "unhold") == 0)
{
auto const part = std::stable_partition(pkgset.begin(), pkgset.end(),
[](pkgCache::VerIterator const &V) { return V.ParentPkg()->SelectedState == pkgCache::State::Hold; });

bool const MarkHold = strcasecmp(CmdL.FileList[0],"hold") == 0;
auto const doneBegin = MarkHold ? pkgset.begin() : part;
auto const doneEnd = MarkHold ? part : pkgset.end();
std::for_each(doneBegin, doneEnd, [&MarkHold](pkgCache::VerIterator const &V) {
if (MarkHold == true)
ioprintf(c1out, _("%s was already set on hold.\n"), V.ParentPkg().FullName(true).c_str());
else
ioprintf(c1out, _("%s was already not hold.\n"), V.ParentPkg().FullName(true).c_str());
});

if (doneBegin == pkgset.begin() && doneEnd == pkgset.end())
return true;

auto const changeBegin = MarkHold ? part : pkgset.begin();
auto const changeEnd = MarkHold ? pkgset.end() : part;
std::move(changeBegin, changeEnd, std::back_inserter(MarkHold ? marks.Hold() : marks.Unhold()));
}
else
{
// FIXME: Maybe show a message for unchanged states here as well?
if (strcasecmp(CmdL.FileList[0], "purge") == 0)
std::swap(marks.Purge(), pkgset);
else if (strcasecmp(CmdL.FileList[0], "deinstall") == 0 || strcasecmp(CmdL.FileList[0], "remove") == 0)
std::swap(marks.Remove(), pkgset);
else //if (strcasecmp(CmdL.FileList[0], "install") == 0)
std::swap(marks.Install(), pkgset);
}
pkgset.clear();

bool success = true;
@@ -211,30 +221,44 @@ static bool DoHold(CommandLine &CmdL)
if (success == false)
_error->Error(_("Executing dpkg failed. Are you root?"));
}

for (auto Ver : marks.Hold())
ioprintf(c1out,_("%s set on hold.\n"), Ver.ParentPkg().FullName(true).c_str());
for (auto Ver : marks.Unhold())
ioprintf(c1out,_("Canceled hold on %s.\n"), Ver.ParentPkg().FullName(true).c_str());

for (auto Ver : marks.Purge())
ioprintf(c1out,_("Selected %s for purge.\n"), Ver.ParentPkg().FullName(true).c_str());
for (auto Ver : marks.Remove())
ioprintf(c1out,_("Selected %s for removal.\n"), Ver.ParentPkg().FullName(true).c_str());
for (auto Ver : marks.Install())
ioprintf(c1out,_("Selected %s for installation.\n"), Ver.ParentPkg().FullName(true).c_str());
return success;
}
/*}}}*/
/* ShowHold - show packages set on hold in dpkg status {{{*/
static bool ShowHold(CommandLine &CmdL)
static bool ShowSelection(CommandLine &CmdL) /*{{{*/
{
pkgCacheFile CacheFile;
pkgCache *Cache = CacheFile.GetPkgCache();
if (unlikely(Cache == NULL))
return false;

pkgCache::State::PkgSelectedState selector;
if (strncasecmp(CmdL.FileList[0], "showpurge", strlen("showpurge")) == 0)
selector = pkgCache::State::Purge;
else if (strncasecmp(CmdL.FileList[0], "showdeinstall", strlen("showdeinstall")) == 0 ||
strncasecmp(CmdL.FileList[0], "showremove", strlen("showremove")) == 0)
selector = pkgCache::State::DeInstall;
else if (strncasecmp(CmdL.FileList[0], "showhold", strlen("showhold")) == 0)
selector = pkgCache::State::Hold;
else //if (strcasecmp(CmdL.FileList[0], "showinstall", strlen("showinstall")) == 0)
selector = pkgCache::State::Install;

std::vector<string> packages;

if (CmdL.FileList[1] == 0)
{
packages.reserve(50); // how many holds are realistic? I hope just a few…
for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P)
if (P->SelectedState == pkgCache::State::Hold)
if (P->SelectedState == selector)
packages.push_back(P.FullName(true));
}
else
@@ -243,7 +267,7 @@ static bool ShowHold(CommandLine &CmdL)
APT::PackageSet pkgset = APT::PackageSet::FromCommandLine(CacheFile, CmdL.FileList + 1, helper);
packages.reserve(pkgset.size());
for (APT::PackageSet::const_iterator P = pkgset.begin(); P != pkgset.end(); ++P)
if (P->SelectedState == pkgCache::State::Hold)
if (P->SelectedState == selector)
packages.push_back(P.FullName(true));
}

@@ -295,15 +319,19 @@ int main(int argc,const char *argv[]) /*{{{*/
CommandLine::Dispatch Cmds[] = {{"help",&ShowHelp},
{"auto",&DoAuto},
{"manual",&DoAuto},
{"hold",&DoHold},
{"unhold",&DoHold},
{"hold",&DoSelection},
{"unhold",&DoSelection},
{"install",&DoSelection},
{"remove",&DoSelection}, // dpkg uses deinstall, but we use remove everywhere else
{"deinstall",&DoSelection},
{"purge",&DoSelection},
{"showauto",&ShowAuto},
{"showmanual",&ShowAuto},
{"showhold",&ShowHold},
// be nice and forgive the typo
{"showholds",&ShowHold},
// be nice and forgive it as it is technical right
{"install",&DoHold},
{"showhold",&ShowSelection}, {"showholds",&ShowSelection},
{"showinstall",&ShowSelection}, {"showinstalls",&ShowSelection},
{"showdeinstall",&ShowSelection}, {"showdeinstalls",&ShowSelection},
{"showremove",&ShowSelection}, {"showremoves",&ShowSelection},
{"showpurge",&ShowSelection}, {"showpurges",&ShowSelection},
// obsolete commands for compatibility
{"markauto", &DoMarkAuto},
{"unmarkauto", &DoMarkAuto},


+ 64
- 46
doc/apt-mark.8.xml View File

@@ -14,7 +14,7 @@
&apt-email;
&apt-product;
<!-- The last update date -->
<date>2012-06-09T00:00:00Z</date>
<date>2015-09-25T00:00:00Z</date>
</refentryinfo>
<refmeta>
@@ -26,31 +26,37 @@
<!-- Man page title -->
<refnamediv>
<refname>apt-mark</refname>
<refpurpose>mark/unmark a package as being automatically-installed</refpurpose>
<refpurpose>show, set and unset various settings for a package</refpurpose>
</refnamediv>

&synopsis-command-apt-mark;

<refsect1><title>Description</title>
<para><command>apt-mark</command> will change whether a package has
been marked as being automatically installed.
<para><command>apt-mark</command> can be used as a unified frontend to set
various settings for a package like marking a package as being
automatically/manually installed or changing <command>dpkg</command>
selections such as hold, install, deinstall and purge which are respected
e.g. by <command>apt-get dselect-upgrade</command> or <command>aptitude</command>.
</para>
</refsect1><refsect1><title>Automatically and manually installed packages</title>
<para>
When you request that a package is installed, and as a result
other packages are installed to satisfy its dependencies, the
dependencies are marked as being automatically installed. Once
these automatically installed packages are no longer depended on
by any manually installed packages, they will be removed by e.g.
<command>apt-get</command> or <command>aptitude</command>.
dependencies are marked as being automatically installed, while
package you installed explicitely is marked as manually installed.
Once a automatically installed package is no longer depended on
by any manually installed package it is considered no longer needed
and e.g. <command>apt-get</command> or <command>aptitude</command>
will at least suggest removing them.
</para>
<variablelist>
<variablelist>
<varlistentry><term><option>auto</option></term>
<listitem><para><literal>auto</literal> is used to mark a
package as being automatically installed, which will cause the
package to be removed when no more manually installed packages
depend on this package.
</para></listitem>
</varlistentry>
</varlistentry>

<varlistentry><term><option>manual</option></term>
<listitem><para><literal>manual</literal> is used to mark a
@@ -58,23 +64,7 @@
package from being automatically removed if no other packages
depend on it.
</para></listitem>
</varlistentry>

<varlistentry><term><option>hold</option></term>
<listitem><para><literal>hold</literal> is used to mark a
package as held back, which will prevent the package from being
automatically installed, upgraded or removed.
The command is only a wrapper around <command>dpkg --set-selections</command>
and the state is therefore maintained by &dpkg; and not affected
by the <option>--file</option> option.
</para></listitem>
</varlistentry>

<varlistentry><term><option>unhold</option></term>
<listitem><para><literal>unhold</literal> is used to cancel a
previously set hold on a package to allow all actions again.
</para></listitem>
</varlistentry>
</varlistentry>

<varlistentry><term><option>showauto</option></term>
<listitem><para><literal>showauto</literal> is used to print a
@@ -82,38 +72,66 @@
All automatically installed packages will be listed if no package is given.
If packages are given only those which are automatically installed will be shown.
</para></listitem>
</varlistentry>
</varlistentry>

<varlistentry><term><option>showmanual</option></term>
<listitem><para><literal>showmanual</literal> can be used in
the same way as <literal>showauto</literal> except that it will print
a list of manually installed packages instead.
</para></listitem>
</varlistentry>
</variablelist>

<refsect2><title>Options</title>
<variablelist>
<varlistentry>
<term><option>-f=&synopsis-param-filename;</option></term>
<term><option>--file=&synopsis-param-filename;</option></term>
<listitem><para>
Read/Write package stats from the filename given with the parameter
&synopsis-param-filename; instead of from the default location, which
is <filename>extended_status</filename> in the directory defined
by the Configuration Item: <literal>Dir::State</literal>.
</para></listitem>
</varlistentry>
</variablelist>
</refsect2>

<varlistentry><term><option>showhold</option></term>
<listitem><para><literal>showhold</literal> is used to print a list
of packages on hold in the same way as for the other show commands.
</refsect1><refsect1><title>Prevent changes for a package</title>
<variablelist>
<varlistentry><term><option>hold</option></term>
<listitem><para><literal>hold</literal> is used to mark a
package as held back, which will prevent the package from being
automatically installed, upgraded or removed.
</para></listitem>
</varlistentry>
</varlistentry>

<varlistentry><term><option>unhold</option></term>
<listitem><para><literal>unhold</literal> is used to cancel a
previously set hold on a package to allow all actions again.
</para></listitem>
</varlistentry>

</variablelist>
</refsect1>
<refsect1><title>options</title>
<variablelist>
<varlistentry>
<term><option>-f=&synopsis-param-filename;</option></term>
<term><option>--file=&synopsis-param-filename;</option></term>
<listitem><para>
Read/Write package stats from the filename given with the parameter
&synopsis-param-filename; instead of from the default location, which
is <filename>extended_status</filename> in the directory defined
by the Configuration Item: <literal>Dir::State</literal>.</para></listitem>
<varlistentry><term><option>showhold</option></term>
<listitem><para><literal>showhold</literal> is used to print a list
of packages on hold in the same way as for the other show commands.
</para></listitem>
</varlistentry>
</variablelist>
</refsect1><refsect1><title>Shedule packages for install, remove and purge</title>
<para>
Some frontends like <command>apt-get dselect-upgrade</command> can be used to
apply previously sheduled changes to the install state of packages. Such changes
can be sheduled with the <option>install</option>, <option>remove</option>
(also known as <option>deinstall</option>) and <option>purge</option> commands.
Packages with a specific selection can be displayed with <option>showinstall</option>,
<option>showremove</option> and <option>showpurge</option> respectively.
More information about these so called dpkg selections can be found in &dpkg;.
</para>
</refsect1>

<refsect1><title>Options</title>
<variablelist>
&apt-commonoptions;
</variablelist>
</refsect1>


+ 35
- 6
doc/apt-verbatim.ent View File

@@ -394,18 +394,47 @@
</cmdsynopsis></refsynopsisdiv>">
<!ENTITY synopsis-command-apt-mark "<refsynopsisdiv><cmdsynopsis>
<command>apt-mark</command>
<arg><option>-f=&synopsis-param-filename;</option></arg>
<group choice='plain'>
<group choice='req'>
<arg choice='plain'><option>-f=&synopsis-param-filename;</option></arg>
<arg choice='plain'>
<group choice='req'>
<arg choice='plain'>auto</arg>
<arg choice='plain'>manual</arg>
</group>
&synopsis-arg-pkg;
</arg>
<arg choice='plain'>
<group choice='req'>
<arg choice='plain'>showauto</arg>
<arg choice='plain'>showmanual</arg>
</group>
<arg choice='opt' rep='repeat'><replaceable>&synopsis-pkg;</replaceable></arg>
</arg>
</group>
&synopsis-help;
</group>
</cmdsynopsis><cmdsynopsis><command>apt-mark</command>
<group choice='plain'>
<arg choice='plain'>
<group choice='req'>
<arg choice='plain'>auto</arg>
<arg choice='plain'>manual</arg>
<arg choice='plain'>showauto</arg>
<arg choice='plain'>showmanual</arg>
<arg choice='plain'>hold</arg>
<arg choice='plain'>unhold</arg>
<arg choice='plain'>install</arg>
<arg choice='plain'>remove</arg>
<arg choice='plain'>purge</arg>
</group>
&synopsis-arg-pkg;
</arg>
&synopsis-help;
<arg choice='plain'>
<group choice='req'>
<arg choice='plain'>showhold</arg>
<arg choice='plain'>showinstall</arg>
<arg choice='plain'>showremove</arg>
<arg choice='plain'>showpurge</arg>
</group>
<arg choice='opt' rep='repeat'><replaceable>&synopsis-pkg;</replaceable></arg>
</arg>
</group>
</cmdsynopsis></refsynopsisdiv>">
<!ENTITY synopsis-command-apt-sortpkgs "<refsynopsisdiv><cmdsynopsis>


+ 40
- 0
test/integration/test-apt-mark View File

@@ -112,3 +112,43 @@ Inst uninstalled (1 unstable [all])
Inst uninstalled-native (1 unstable [amd64])
Conf uninstalled (1 unstable [all])
Conf uninstalled-native (1 unstable [amd64])' aptget install uninstalled uninstalled-native -s
testsuccess aptmark unhold uninstalled uninstalled-native

testselections() {
testsuccess aptmark hold "$1"
testsuccessequal "$1" aptmark showholds "$1"
testsuccess aptmark unhold "$1"
testsuccessequal "$1" aptmark showinstalls "$1"
testsuccess aptmark hold "$1"
testsuccessequal "$1" aptmark showholds "$1"
testsuccess aptmark install "$1"
testsuccessequal "$1" aptmark showinstalls "$1"
testsuccess aptmark remove "$1"
testsuccessequal "$1" aptmark showremoves "$1"
testsuccess aptmark purge "$1"
testsuccessequal "$1" aptmark showpurges "$1"
}
testselections 'foo'
testselections 'bar'

testsuccessequal 'Reading package lists...
Building dependency tree...
Reading state information...
The following packages will be REMOVED:
bar* foo*
0 upgraded, 0 newly installed, 2 to remove and 0 not upgraded.
Purg bar [1]
Purg foo [1]' aptget dselect-upgrade -s

testuninstalledselections() {
testsuccess aptmark hold "$1"
testsuccessequal "$1" aptmark showholds "$1"
testsuccess aptmark unhold "$1"
testsuccessequal "$1" aptmark showremoves "$1"
testsuccess aptmark hold "$1"
testsuccessequal "$1" aptmark showholds "$1"
testsuccess aptmark install "$1"
testsuccessequal "$1" aptmark showinstalls "$1"
}
testuninstalledselections 'uninstalled'
testuninstalledselections 'uninstalled-native'

Loading…
Cancel
Save