Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unittest framework and some dependency tests #2759

Merged
merged 8 commits into from
Dec 27, 2023
151 changes: 90 additions & 61 deletions source/dub/dub.d
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ static immutable string[] defaultRegistryURLs = [

See_Also: `defaultRegistryURLs`
*/
deprecated("This function wasn't intended for public use - open an issue with Dub if you need it")
PackageSupplier[] defaultPackageSuppliers()
{
logDiagnostic("Using dub registry url '%s'", defaultRegistryURLs[0]);
Expand All @@ -70,6 +71,7 @@ PackageSupplier[] defaultPackageSuppliers()

Allowed protocols are dub+http(s):// and maven+http(s)://.
*/
deprecated("This function wasn't intended for public use - open an issue with Dub if you need it")
PackageSupplier getRegistryPackageSupplier(string url)
{
switch (url.startsWith("dub+", "mvn+", "file://"))
Expand All @@ -85,7 +87,7 @@ PackageSupplier getRegistryPackageSupplier(string url)
}
}

unittest
deprecated unittest
{
auto dubRegistryPackageSupplier = getRegistryPackageSupplier("dub+https://code.dlang.org");
assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org"));
Expand All @@ -107,7 +109,7 @@ unittest
the command line interface.
*/
class Dub {
private {
protected {
bool m_dryRun = false;
PackageManager m_packageManager;
PackageSupplier[] m_packageSuppliers;
Expand Down Expand Up @@ -135,23 +137,24 @@ class Dub {

Params:
root_path = Path to the root package
additional_package_suppliers = A list of package suppliers to try
before the suppliers found in the configurations files and the
`defaultPackageSuppliers`.
skip_registry = Can be used to skip using the configured package
suppliers, as well as the default suppliers.
base = A list of package suppliers that are always present
(regardless of `skip`) and take precedence over the default
and configured `PackageSupplier`. This setting is currently
not used by the dub application but useful for libraries.
skip = Can be used to skip using the configured package suppliers,
as well as the default suppliers.
*/
this(string root_path = ".", PackageSupplier[] additional_package_suppliers = null,
SkipPackageSuppliers skip_registry = SkipPackageSuppliers.none)
this(string root_path = ".", PackageSupplier[] base = null,
SkipPackageSuppliers skip = SkipPackageSuppliers.none)
{
m_rootPath = NativePath(root_path);
if (!m_rootPath.absolute) m_rootPath = getWorkingDirectory() ~ m_rootPath;

init();

m_packageSuppliers = this.computePkgSuppliers(additional_package_suppliers,
skip_registry, environment.get("DUB_REGISTRY", null));
m_packageManager = new PackageManager(m_rootPath, m_dirs.userPackages, m_dirs.systemSettings, false);
const registry_var = environment.get("DUB_REGISTRY", null);
m_packageSuppliers = this.makePackageSuppliers(base, skip, registry_var);
m_packageManager = this.makePackageManager();

auto ccps = m_config.customCachePaths;
if (ccps.length)
Expand Down Expand Up @@ -194,7 +197,20 @@ class Dub {
this(pkg_root, pkg_root);
}

private void init()
/**
* Get the `PackageManager` instance to use for this `Dub` instance
*
* The `PackageManager` is a central component of `Dub` as it allows to
* store and retrieve packages from the file system. In unittests, or more
* generally in a library setup, one may wish to provide a custom
* implementation, which can be done by overriding this method.
*/
protected PackageManager makePackageManager() const
{
return new PackageManager(m_rootPath, m_dirs.userPackages, m_dirs.systemSettings, false);
}

protected void init()
{
this.m_dirs = SpecialDirs.make();
this.m_config = this.loadConfig(this.m_dirs);
Expand Down Expand Up @@ -298,67 +314,100 @@ class Dub {
additional_package_suppliers = A list of package suppliers to try
before the suppliers found in the configurations files and the
`defaultPackageSuppliers`.
skip_registry = Can be used to skip using the configured package
suppliers, as well as the default suppliers.
skip = Can be used to skip using the configured package suppliers,
as well as the default suppliers.
*/
deprecated("This is an implementation detail. " ~
"Use `packageSuppliers` to get the computed list of package " ~
"suppliers once a `Dub` instance has been constructed.")
public PackageSupplier[] getPackageSuppliers(PackageSupplier[] additional_package_suppliers, SkipPackageSuppliers skip_registry)
public PackageSupplier[] getPackageSuppliers(PackageSupplier[] base, SkipPackageSuppliers skip)
{
return this.computePkgSuppliers(additional_package_suppliers, skip_registry, environment.get("DUB_REGISTRY", null));
return this.makePackageSuppliers(base, skip, environment.get("DUB_REGISTRY", null));
}

/// Ditto
private PackageSupplier[] computePkgSuppliers(
PackageSupplier[] additional_package_suppliers, SkipPackageSuppliers skip_registry,
string dub_registry_var)
protected PackageSupplier[] makePackageSuppliers(PackageSupplier[] base,
SkipPackageSuppliers skip, string registry_var)
{
PackageSupplier[] ps = additional_package_suppliers;
PackageSupplier[] ps = base;

if (skip_registry < SkipPackageSuppliers.all)
if (skip < SkipPackageSuppliers.all)
{
ps ~= dub_registry_var
ps ~= registry_var
.splitter(";")
.map!(url => getRegistryPackageSupplier(url))
.map!(url => this.makePackageSupplier(url))
.array;
}

if (skip_registry < SkipPackageSuppliers.configured)
if (skip < SkipPackageSuppliers.configured)
{
ps ~= m_config.registryUrls
.map!(url => getRegistryPackageSupplier(url))
.map!(url => this.makePackageSupplier(url))
.array;
}

if (skip_registry < SkipPackageSuppliers.standard)
ps ~= defaultPackageSuppliers();
if (skip < SkipPackageSuppliers.standard)
ps ~= new FallbackPackageSupplier(
defaultRegistryURLs.map!(url => this.makePackageSupplier(url))
.array);

return ps;
}

// Note: This test rely on the environment, which is not how unittests should work.
// This should be removed / refactored to keep coverage without affecting the env.
unittest
{
import dub.test.base : TestDub;

scope (exit) environment.remove("DUB_REGISTRY");
auto dub = new TestDub(".", null, SkipPackageSuppliers.configured);
assert(dub.m_packageSuppliers.length == 0);
assert(dub.packageSuppliers.length == 0);
environment["DUB_REGISTRY"] = "http://example.com/";
dub = new TestDub(".", null, SkipPackageSuppliers.configured);
assert(dub.m_packageSuppliers.length == 1);
assert(dub.packageSuppliers.length == 1);
environment["DUB_REGISTRY"] = "http://example.com/;http://foo.com/";
dub = new TestDub(".", null, SkipPackageSuppliers.configured);
assert(dub.m_packageSuppliers.length == 2);
assert(dub.packageSuppliers.length == 2);
dub = new TestDub(".", [new RegistryPackageSupplier(URL("http://bar.com/"))], SkipPackageSuppliers.configured);
assert(dub.m_packageSuppliers.length == 3);
assert(dub.packageSuppliers.length == 3);

dub = new TestDub();
assert(dub.computePkgSuppliers(null, SkipPackageSuppliers.none, null).length == 1);
assert(dub.computePkgSuppliers(null, SkipPackageSuppliers.configured, null).length == 0);
assert(dub.computePkgSuppliers(null, SkipPackageSuppliers.standard, null).length == 0);
assert(dub.computePkgSuppliers(null, SkipPackageSuppliers.standard, "http://example.com/")
assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.none, null).length == 1);
assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.configured, null).length == 0);
assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.standard, null).length == 0);
assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.standard, "http://example.com/")
.length == 1);
}

/**
* Instantiate a `PackageSupplier` according to a given URL
*
* This is a factory function for `PackageSupplier`. Child classes may
* wish to override this to implement their own `PackageSupplier` logic,
* be it by extending this method's ability or replacing it.
*
* Params:
* url = The URL of the `PackageSupplier`.
*
* Returns:
* A new instance of a `PackageSupplier`.
*/
protected PackageSupplier makePackageSupplier(string url) const
{
switch (url.startsWith("dub+", "mvn+", "file://"))
{
case 1:
return new RegistryPackageSupplier(URL(url[4..$]));
case 2:
return new MavenRegistryPackageSupplier(URL(url[4..$]));
case 3:
return new FileSystemPackageSupplier(NativePath(url[7..$]));
default:
return new RegistryPackageSupplier(URL(url));
}
}

/// ditto
deprecated("This is an implementation detail. " ~
"Use `packageSuppliers` to get the computed list of package " ~
Expand Down Expand Up @@ -1458,9 +1507,13 @@ class Dub {
return compilers[0];
}

// This test also relies on the environment and the filesystem,
// as the `makePackageSuppliers` does, and should be refactored.
unittest
{
import dub.test.base : TestDub;
import std.path: buildPath, absolutePath;

auto dub = new TestDub(".", null, SkipPackageSuppliers.configured);
immutable olddc = environment.get("DC", null);
immutable oldpath = environment.get("PATH", null);
Expand Down Expand Up @@ -1832,31 +1885,7 @@ private class DependencyVersionResolver : DependencyResolver!(Dependency, Depend
}
}

/**
* An instance of Dub that does not rely on the environment
*
* This instance of dub should not read any environment variables,
* nor should it do any file IO, to make it usable and reliable in unittests.
* Currently it reads environment variables but does not read the configuration.
*/
version(unittest) package class TestDub : Dub
{
/// Forward to base constructor
public this (string root = ".", PackageSupplier[] extras = null,
SkipPackageSuppliers skip = SkipPackageSuppliers.none)
{
super(root, extras, skip);
}

/// Avoid loading user configuration
protected override Settings loadConfig(ref SpecialDirs dirs) const
{
// No-op
return Settings.init;
}
}

private struct SpecialDirs {
package struct SpecialDirs {
/// The path where to store temporary files and directory
NativePath temp;
/// The system-wide dub-specific folder
Expand Down
25 changes: 20 additions & 5 deletions source/dub/packagemanager.d
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class PackageManager {
* Returns:
* A `Package` if one was found, `null` if none exists.
*/
private Package lookup (string name, Version vers) {
protected Package lookup (string name, Version vers) {
if (!this.m_initialized)
this.refresh();

Expand Down Expand Up @@ -243,6 +243,21 @@ class PackageManager {
return this.lookup(name, ver);
}

/**
* Adds a `Package` to this `PackageManager`
*
* This is currently only available in unittests as it is a convenience
* function used by `TestDub`, but could be generalized once IO has been
* abstracted away from this class.
*/
version (unittest) Package add(Package pkg)
{
// See `PackageManager.addPackages` for inspiration.
assert(!pkg.subPackages.length, "Subpackages are not yet supported");
this.m_internal.fromPath ~= pkg;
return pkg;
}

/// ditto
deprecated("Use the overload that accepts a `Version` as second argument")
Package getPackage(string name, string ver, bool enable_overrides = true)
Expand Down Expand Up @@ -1121,7 +1136,7 @@ private enum LocalOverridesFilename = "local-overrides.json";
* Additionally, each location host a config file,
* which is not managed by this module, but by dub itself.
*/
private struct Location {
package struct Location {
/// The absolute path to the root of the location
NativePath packagePath;

Expand Down Expand Up @@ -1364,7 +1379,7 @@ private struct Location {
* Returns:
* A `Package` if one was found, `null` if none exists.
*/
private inout(Package) lookup(string name, Version ver, PackageManager mgr) inout {
inout(Package) lookup(string name, Version ver, PackageManager mgr) inout {
foreach (pkg; this.localPackages)
if (pkg.name == name && pkg.version_.matches(ver, VersionMatchMode.standard))
return pkg;
Expand All @@ -1391,7 +1406,7 @@ private struct Location {
* Returns:
* A `Package` if one was found, `null` if none exists.
*/
private Package load (string name, Version vers, PackageManager mgr)
Package load (string name, Version vers, PackageManager mgr)
{
if (auto pkg = this.lookup(name, vers, mgr))
return pkg;
Expand Down Expand Up @@ -1423,7 +1438,7 @@ private struct Location {
* but this function returns `$BASE/$NAME-$VERSION/`
* `$BASE` is `this.packagePath`.
*/
private NativePath getPackagePath (string name, string vers)
NativePath getPackagePath (string name, string vers)
{
NativePath result = this.packagePath ~ name ~ vers;
result.endsWithSlash = true;
Expand Down
4 changes: 2 additions & 2 deletions source/dub/project.d
Original file line number Diff line number Diff line change
Expand Up @@ -1734,8 +1734,8 @@ unittest
This is the runtime representation of the information contained in
"dub.selections.json" within a package's directory.
*/
final class SelectedVersions {
private {
public class SelectedVersions {
protected {
enum FileVersion = 1;
Selected m_selections;
bool m_dirty = false; // has changes since last save
Expand Down
Loading
Loading