diff --git a/Makefile b/Makefile index 4a21e1f..a8b840b 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,12 @@ GOVERSION=1.22.0 DIST_DIR=dist +SHARED_LIB_TEST_CC=gcc +SHARED_LIB_TEST_CFLAGS=-Wall -ldl +SHARED_LIB_TEST_TARGET=$(DIST_DIR)/sharedlibrary_test +SHARED_LIB_TEST_DIR=./sharedlibrary/tests +SHARED_LIB_TEST_SOURCES=$(wildcard $(SHARED_LIB_TEST_DIR)/*.c) + all: test build-all build-all: build-darwin-arm64 build-darwin-amd64 build-linux-amd64 build-linux-arm64 build-windows-amd64 build-windows-arm64 @@ -45,6 +51,14 @@ build-sharedlib-linux-amd64: @echo "Building shared library for Linux x64..." @GOOS=linux GOARCH=amd64 $(GOBUILD) $(SHARED_LIB_OPT) -o $(DIST_DIR)/$(BINARY_NAME)-linux-amd64.so $(SHARED_LIB_LOCATION) +build-sharedlib-tests: build-sharedlib-linux-amd64 + @echo "Building shared library tests..." + @$(SHARED_LIB_TEST_CC) $(SHARED_LIB_TEST_CFLAGS) -o $(SHARED_LIB_TEST_TARGET) $(SHARED_LIB_TEST_SOURCES) + +run-sharedlib-tests: build-sharedlib-tests + @echo "Running shared library tests..." + @$(SHARED_LIB_TEST_TARGET) $(DIST_DIR)/$(BINARY_NAME)-linux-amd64.so + xgo-compile-sharedlib: @echo "Building shared libraries using xgo..." @mkdir -p $(DIST_DIR) diff --git a/api/config/config.go b/api/config/config.go index 3553361..ec1b878 100644 --- a/api/config/config.go +++ b/api/config/config.go @@ -57,6 +57,18 @@ func (c *ServerConfig) PopulateCalculatedFields() { logger.EnableDebugOutput = c.Debug } +func (c *ServerConfig) ApplyDefaultsToEmptyFields() { + if c.Host == "" { + c.Host = "localhost" + } + if c.Port == 0 { + c.Port = 8081 + } + if c.AccountKey == "" { + c.AccountKey = DefaultAccountKey + } +} + func setFlagsFromEnvironment() (err error) { flag.VisitAll(func(f *flag.Flag) { name := EnvPrefix + strings.ToUpper(strings.Replace(f.Name, "-", "_", -1)) diff --git a/api/config/models.go b/api/config/models.go index 9679ab6..9d577da 100644 --- a/api/config/models.go +++ b/api/config/models.go @@ -1,20 +1,20 @@ package config type ServerConfig struct { - DatabaseAccount string - DatabaseDomain string - DatabaseEndpoint string - AccountKey string + DatabaseAccount string `json:"databaseAccount"` + DatabaseDomain string `json:"databaseDomain"` + DatabaseEndpoint string `json:"databaseEndpoint"` + AccountKey string `json:"accountKey"` - ExplorerPath string - Port int - Host string - TLS_CertificatePath string - TLS_CertificateKey string - InitialDataFilePath string - PersistDataFilePath string - DisableAuth bool - DisableTls bool - Debug bool - ExplorerBaseUrlLocation string + ExplorerPath string `json:"explorerPath"` + Port int `json:"port"` + Host string `json:"host"` + TLS_CertificatePath string `json:"tlsCertificatePath"` + TLS_CertificateKey string `json:"tlsCertificateKey"` + InitialDataFilePath string `json:"initialDataFilePath"` + PersistDataFilePath string `json:"persistDataFilePath"` + DisableAuth bool `json:"disableAuth"` + DisableTls bool `json:"disableTls"` + Debug bool `json:"debug"` + ExplorerBaseUrlLocation string `json:"explorerBaseUrlLocation"` } diff --git a/internal/repositories/state.go b/internal/repositories/state.go index 0c0438b..79c5757 100644 --- a/internal/repositories/state.go +++ b/internal/repositories/state.go @@ -39,20 +39,33 @@ func (r *DataRepository) LoadStateFS(filePath string) { return } - var state repositorymodels.State - if err := json.Unmarshal(data, &state); err != nil { + err = r.LoadStateJSON(string(data)) + if err != nil { log.Fatalf("Error unmarshalling state JSON: %v", err) - return + } +} + +func (r *DataRepository) LoadStateJSON(jsonData string) error { + r.storeState.RLock() + defer r.storeState.RUnlock() + + var state repositorymodels.State + if err := json.Unmarshal([]byte(jsonData), &state); err != nil { + return err } - logger.Info("Loaded state:") - logger.Infof("Databases: %d\n", getLength(state.Databases)) - logger.Infof("Collections: %d\n", getLength(state.Collections)) - logger.Infof("Documents: %d\n", getLength(state.Documents)) - - r.storeState = state + r.storeState.Collections = state.Collections + r.storeState.Databases = state.Databases + r.storeState.Documents = state.Documents r.ensureStoreStateNoNullReferences() + + logger.Info("Loaded state:") + logger.Infof("Databases: %d\n", getLength(r.storeState.Databases)) + logger.Infof("Collections: %d\n", getLength(r.storeState.Collections)) + logger.Infof("Documents: %d\n", getLength(r.storeState.Documents)) + + return nil } func (r *DataRepository) SaveStateFS(filePath string) { diff --git a/sharedlibrary/sharedlibrary.go b/sharedlibrary/sharedlibrary.go index 60836cc..768667e 100644 --- a/sharedlibrary/sharedlibrary.go +++ b/sharedlibrary/sharedlibrary.go @@ -22,6 +22,7 @@ const ( ResponseServerInstanceAlreadyExists = 2 ResponseFailedToParseConfiguration = 3 ResponseServerInstanceNotFound = 4 + ResponseFailedToLoadState = 5 ) //export CreateServerInstance @@ -44,6 +45,7 @@ func CreateServerInstance(serverName *C.char, configurationJSON *C.char) int { } configuration.PopulateCalculatedFields() + configuration.ApplyDefaultsToEmptyFields() repository := repositories.NewDataRepository(repositories.RepositoryOptions{ InitialDataFilePath: configuration.InitialDataFilePath, @@ -85,4 +87,20 @@ func GetServerInstanceState(serverName *C.char) *C.char { return nil } +//export LoadServerInstanceState +func LoadServerInstanceState(serverName *C.char, stateJSON *C.char) int { + serverNameStr := C.GoString(serverName) + stateJSONStr := C.GoString(stateJSON) + + if serverInstance, ok := serverInstances[serverNameStr]; ok { + err := serverInstance.repository.LoadStateJSON(stateJSONStr) + if err != nil { + return ResponseFailedToLoadState + } + return ResponseSuccess + } + + return ResponseServerInstanceNotFound +} + func main() {} diff --git a/sharedlibrary/tests/main.c b/sharedlibrary/tests/main.c new file mode 100644 index 0000000..bf226ee --- /dev/null +++ b/sharedlibrary/tests/main.c @@ -0,0 +1,30 @@ +#include "shared.h" + +void test_CreateServerInstance(); +void test_StopServerInstance(); +void test_ServerInstanceStateMethods(); + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + + const char *libPath = argv[1]; + handle = dlopen(libPath, RTLD_LAZY); + if (!handle) + { + fprintf(stderr, "Failed to load shared library: %s\n", dlerror()); + return EXIT_FAILURE; + } + + printf("Running tests for library: %s\n", libPath); + test_CreateServerInstance(); + test_ServerInstanceStateMethods(); + test_StopServerInstance(); + + dlclose(handle); + return EXIT_SUCCESS; +} diff --git a/sharedlibrary/tests/shared.c b/sharedlibrary/tests/shared.c new file mode 100644 index 0000000..36ac210 --- /dev/null +++ b/sharedlibrary/tests/shared.c @@ -0,0 +1,36 @@ +#include "shared.h" + +void *handle = NULL; + +void *load_function(const char *func_name) +{ + void *func = dlsym(handle, func_name); + if (!func) + { + fprintf(stderr, "Failed to load function %s: %s\n", func_name, dlerror()); + } + return func; +} + +char *compact_json(const char *json) +{ + size_t len = strlen(json); + char *compact = (char *)malloc(len + 1); + if (!compact) + { + fprintf(stderr, "Failed to allocate memory for compacted JSON\n"); + return NULL; + } + + char *dest = compact; + for (const char *src = json; *src != '\0'; ++src) + { + if (!isspace((unsigned char)*src)) // Skip spaces, newlines, tabs, etc. + { + *dest++ = *src; + } + } + *dest = '\0'; // Null-terminate the string + + return compact; +} diff --git a/sharedlibrary/tests/shared.h b/sharedlibrary/tests/shared.h new file mode 100644 index 0000000..76b7508 --- /dev/null +++ b/sharedlibrary/tests/shared.h @@ -0,0 +1,15 @@ +#ifndef SHARED_H +#define SHARED_H + +#include +#include +#include +#include +#include + +extern void *handle; + +void *load_function(const char *func_name); +char *compact_json(const char *json); + +#endif diff --git a/sharedlibrary/tests/test_create.c b/sharedlibrary/tests/test_create.c new file mode 100644 index 0000000..aff2fb6 --- /dev/null +++ b/sharedlibrary/tests/test_create.c @@ -0,0 +1,26 @@ +#include "shared.h" + +void test_CreateServerInstance() +{ + typedef int (*CreateServerInstanceFn)(char *, char *); + CreateServerInstanceFn CreateServerInstance = (CreateServerInstanceFn)load_function("CreateServerInstance"); + + if (!CreateServerInstance) + { + fprintf(stderr, "Failed to find CreateServerInstance function\n"); + return; + } + + char *serverName = "TestServer"; + char *configJSON = "{\"host\":\"localhost\",\"port\":8080}"; + + int result = CreateServerInstance(serverName, configJSON); + if (result == 0) + { + printf("CreateServerInstance: SUCCESS\n"); + } + else + { + printf("CreateServerInstance: FAILED (result = %d)\n", result); + } +} diff --git a/sharedlibrary/tests/test_instance_state.c b/sharedlibrary/tests/test_instance_state.c new file mode 100644 index 0000000..cc70d69 --- /dev/null +++ b/sharedlibrary/tests/test_instance_state.c @@ -0,0 +1,64 @@ +#include "shared.h" + +void test_ServerInstanceStateMethods() +{ + typedef int (*LoadServerInstanceStateFn)(char *, char *); + LoadServerInstanceStateFn LoadServerInstanceState = (LoadServerInstanceStateFn)load_function("LoadServerInstanceState"); + if (!LoadServerInstanceState) + { + fprintf(stderr, "Failed to find LoadServerInstanceState function\n"); + return; + } + + char *serverName = "TestServer"; + char *stateJSON = "{\"databases\":{\"test-db\":{\"id\":\"test-db\"}}}"; + int result = LoadServerInstanceState(serverName, stateJSON); + if (result == 0) + { + printf("LoadServerInstanceState: SUCCESS\n"); + } + else + { + printf("LoadServerInstanceState: FAILED (result = %d)\n", result); + } + + typedef char *(*GetServerInstanceStateFn)(char *); + GetServerInstanceStateFn GetServerInstanceState = (GetServerInstanceStateFn)load_function("GetServerInstanceState"); + if (!GetServerInstanceState) + { + fprintf(stderr, "Failed to find GetServerInstanceState function\n"); + return; + } + + char *state = GetServerInstanceState(serverName); + if (state) + { + printf("GetServerInstanceState: SUCCESS (state = %s)\n", state); + } + else + { + printf("GetServerInstanceState: FAILED\n"); + } + + const char *expected_state = "{\"databases\":{\"test-db\":{\"id\":\"test-db\",\"_ts\":0,\"_rid\":\"\",\"_etag\":\"\",\"_self\":\"\"}},\"collections\":{\"test-db\":{}},\"documents\":{\"test-db\":{}}}"; + char *compact_state = compact_json(state); + if (!compact_state) + { + free(state); + return; + } + + if (strcmp(compact_state, expected_state) == 0) + { + printf("GetServerInstanceState: State matches expected value.\n"); + } + else + { + printf("GetServerInstanceState: State does not match expected value.\n"); + printf("Expected: %s\n", expected_state); + printf("Actual: %s\n", compact_state); + } + + free(state); + free(compact_state); +} diff --git a/sharedlibrary/tests/test_stop.c b/sharedlibrary/tests/test_stop.c new file mode 100644 index 0000000..bfaf646 --- /dev/null +++ b/sharedlibrary/tests/test_stop.c @@ -0,0 +1,24 @@ +#include "shared.h" + +void test_StopServerInstance() +{ + typedef int (*StopServerInstanceFn)(char *); + StopServerInstanceFn StopServerInstance = (StopServerInstanceFn)load_function("StopServerInstance"); + + if (!StopServerInstance) + { + fprintf(stderr, "Failed to find StopServerInstance function\n"); + return; + } + + char *serverName = "TestServer"; + int result = StopServerInstance(serverName); + if (result == 0) + { + printf("StopServerInstance: SUCCESS\n"); + } + else + { + printf("StopServerInstance: FAILED (result = %d)\n", result); + } +}