Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/system/dmod_system.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,41 @@ Dmod_Context_t* Dmod_LoadFile( const char* Path )
return NULL;
}

// Check if path is in [package_name]/module_name format
if( Path[0] == '[' )
{
const char* closeBracket = strchr(Path, ']');
if( closeBracket != NULL && closeBracket[1] == '/' && closeBracket[2] != '\0' )
{
// Extract package name (between [ and ])
size_t packageNameLen = closeBracket - Path - 1;
char packageName[DMOD_MAX_PACKAGE_NAME_LENGTH];
if( packageNameLen > 0 && packageNameLen < DMOD_MAX_PACKAGE_NAME_LENGTH )
{
memcpy( packageName, Path + 1, packageNameLen );
packageName[packageNameLen] = '\0';

// Module name starts after ]/
const char* moduleName = closeBracket + 2;

// Find the package slot first
Dmod_PackageSlot_t* slot = Dmod_Pck_FindSlotByName( packageName, NULL );
if( slot != NULL && Dmod_Pck_IsValidSlot( slot ) )
{
// Use the package name from the slot (persistent pointer)
const char* persistentPackageName = Dmod_Pck_GetPackageName( slot );
DMOD_LOG_INFO("Loading module '%s' from package '%s'\n", moduleName, persistentPackageName);
return Dmod_LoadFromPackage( persistentPackageName, moduleName );
}
else
{
DMOD_LOG_ERROR("Cannot load module - package not found: %s\n", packageName);
return NULL;
}
}
}
}

if( Dmod_IsDMPFile(Path) )
{
uint32_t nIndex = UINT32_MAX;
Expand Down
2 changes: 1 addition & 1 deletion src/system/private/dmod_pck.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Dmod_PackageSlot_t* Dmod_Pck_FindSlotByName( const char* PackageName, uint32_t *
for( size_t i = 0; i < DMOD_MAX_NUMBER_OF_PACKAGES; i++ )
{
const char* name = Dmod_Pck_GetPackageName( &Dmod_Packages[i] );
if( strcmp( name, PackageName ) == 0 )
if( name != NULL && strcmp( name, PackageName ) == 0 )
{
if( outIndex != NULL )
{
Expand Down
1 change: 1 addition & 0 deletions tests/system/public/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ set(TEST_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_arch.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_findmatch.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_readnextmodule.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_loadfile_package_path.cpp
PARENT_SCOPE
)
206 changes: 206 additions & 0 deletions tests/system/public/tests_dmod_loadfile_package_path.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#define DMOD_PRIVATE
#include <stdlib.h>
#include <string.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "dmod.h"
#include "dmod_system.h"
#include "private/dmod_pck.h"

// ===============================================================
// Test fixture
// ===============================================================

class DmodLoadFilePackagePathTest : public ::testing::Test
{
protected:
void SetUp() override
{
Dmod_Initialize();
}

void TearDown() override
{
Dmod_Deinitialize();
}
};

// ===============================================================
// Tests for [package]/module path support
// ===============================================================

/**
* @brief Test that normal file paths are not affected by [package]/module parsing
*
* This test verifies backward compatibility - normal file paths should still work
* as expected and not be interpreted as package paths.
*/
TEST_F(DmodLoadFilePackagePathTest, NormalPathsNotAffectedByPackageParsing)
{
// These paths should not be interpreted as [package]/module format
const char* normalPaths[] = {
"regular_module.dmf",
"./relative/path/module.dmf",
"/absolute/path/module.dmf",
"module_without_extension",
"[incomplete_bracket",
"[package]no_slash",
"[package]/", // Missing module name
"[]module", // Empty package name
NULL
};

for (int i = 0; normalPaths[i] != NULL; i++)
{
const char* path = normalPaths[i];

// Verify the path detection logic
bool startsWithBracket = (path[0] == '[');
const char* closeBracket = strchr(path, ']');
bool hasValidFormat = (startsWithBracket &&
closeBracket != NULL &&
closeBracket[1] == '/' &&
closeBracket[2] != '\0' &&
(closeBracket - path - 1) > 0);

// Only paths with valid [package]/module format should be parsed as such
if (strcmp(path, "[package]/module") == 0 || strcmp(path, "[pkg]/mod") == 0)
{
EXPECT_TRUE(hasValidFormat) << "Path '" << path << "' should be valid package format";
}
else if (startsWithBracket)
{
EXPECT_FALSE(hasValidFormat) << "Path '" << path << "' should NOT be valid package format";
}
}
}

/**
* @brief Test [package]/module path format detection
*
* This test verifies that valid [package]/module paths are correctly identified.
*/
TEST_F(DmodLoadFilePackagePathTest, ValidPackagePathFormatDetection)
{
// These paths should be recognized as [package]/module format
const char* validPaths[] = {
"[test_package]/MyLibrary",
"[pkg]/module",
"[a]/b",
"[very_long_package_name]/module_name",
NULL
};

for (int i = 0; validPaths[i] != NULL; i++)
{
const char* path = validPaths[i];

// Verify the format is detected
bool startsWithBracket = (path[0] == '[');
const char* closeBracket = strchr(path, ']');
bool hasValidFormat = (startsWithBracket &&
closeBracket != NULL &&
closeBracket[1] == '/' &&
closeBracket[2] != '\0' &&
(closeBracket - path - 1) > 0);

EXPECT_TRUE(hasValidFormat) << "Path '" << path << "' should be recognized as valid [package]/module format";

if (hasValidFormat)
{
// Extract package name length
size_t packageNameLen = closeBracket - path - 1;
EXPECT_GT(packageNameLen, 0u) << "Package name should not be empty";
EXPECT_LT(packageNameLen, DMOD_MAX_PACKAGE_NAME_LENGTH) << "Package name should fit in buffer";
}
}
}

/**
* @brief Test loading from [package]/module path when package exists
*
* This test verifies that Dmod_LoadFile can successfully load a module
* from a package using the [package]/module path format.
*/
TEST_F(DmodLoadFilePackagePathTest, LoadFromPackagePath)
{
// First, load a DMP package
uint32_t packageIndex = UINT32_MAX;
const char* packagePath = "/tmp/test_package.dmp";

// Check if test package exists
if (!Dmod_FileAvailable(packagePath))
{
GTEST_SKIP() << "Test package not available at " << packagePath;
}

bool packageLoaded = Dmod_AddPackageFile(packagePath, &packageIndex);
if (!packageLoaded)
{
GTEST_SKIP() << "Failed to load test package";
}

ASSERT_NE(packageIndex, UINT32_MAX) << "Package index should be valid";

// Now try to load a module using [package]/module format
Dmod_Context_t* ctx = Dmod_LoadFile("[test_package]/MyLibrary");

ASSERT_NE(ctx, nullptr) << "Should be able to load module from package using [package]/module format";

if (ctx != NULL)
{
// Verify we loaded the correct module
const char* moduleName = Dmod_GetName(ctx);
EXPECT_STREQ(moduleName, "MyLibrary") << "Loaded module should have correct name";

// Verify package name is set
EXPECT_STREQ(ctx->PackageName, "test_package") << "Module should have correct package name";

// Clean up
Dmod_Unload(ctx, true);
}
}

/**
* @brief Test that [package]/module path fails gracefully when package doesn't exist
*
* This test verifies that trying to load from a non-existent package
* returns NULL instead of crashing.
*/
TEST_F(DmodLoadFilePackagePathTest, NonExistentPackageFails)
{
// Try to load from a package that doesn't exist
Dmod_Context_t* ctx = Dmod_LoadFile("[nonexistent_package]/some_module");

EXPECT_EQ(ctx, nullptr) << "Loading from non-existent package should return NULL";
}

/**
* @brief Test that [package]/module path fails gracefully when module doesn't exist in package
*
* This test verifies that trying to load a non-existent module from a package
* returns NULL instead of crashing.
*/
TEST_F(DmodLoadFilePackagePathTest, NonExistentModuleInPackageFails)
{
// First, load a DMP package
uint32_t packageIndex = UINT32_MAX;
const char* packagePath = "/tmp/test_package.dmp";

// Check if test package exists
if (!Dmod_FileAvailable(packagePath))
{
GTEST_SKIP() << "Test package not available at " << packagePath;
}

bool packageLoaded = Dmod_AddPackageFile(packagePath, &packageIndex);
if (!packageLoaded)
{
GTEST_SKIP() << "Failed to load test package";
}

// Try to load a module that doesn't exist in the package
Dmod_Context_t* ctx = Dmod_LoadFile("[test_package]/NonExistentModule");

EXPECT_EQ(ctx, nullptr) << "Loading non-existent module from package should return NULL";
}
Loading