From a8496154df900cf8bc6315cbc7844aa28b88a319 Mon Sep 17 00:00:00 2001 From: Sam Yuan Date: Wed, 15 Jan 2025 10:45:34 +0800 Subject: [PATCH 1/9] just dummy change to get test cov Signed-off-by: Sam Yuan --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 52c84dad9c..eeb284f9e0 100644 --- a/README.md +++ b/README.md @@ -168,3 +168,5 @@ dual licensed as above, without any additional terms or conditions. ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=sustainable-computing-io/kepler&type=Date)](https://star-history.com/#sustainable-computing-io/kepler&Date) + +just dummy change \ No newline at end of file From 24dd04cdfd7cd2fa3f16bbc9474b903185c1f89a Mon Sep 17 00:00:00 2001 From: Sam Yuan Date: Wed, 15 Jan 2025 10:48:07 +0800 Subject: [PATCH 2/9] add for dispatch CI for exp Signed-off-by: Sam Yuan --- .github/workflows/dispatch.yml | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/dispatch.yml diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml new file mode 100644 index 0000000000..173d065b47 --- /dev/null +++ b/.github/workflows/dispatch.yml @@ -0,0 +1,39 @@ +name: Dispatch Trigger as expirement for SamYuan1990/OpenAI_CodeAgent-action + +on: + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +jobs: + test-action: + name: GitHub Actions Test + runs-on: ubuntu-latest + + steps: + - name: Checkout + id: checkout + uses: actions/checkout@v4 + + - name: Test Local Action + id: test-action + uses: SamYuan1990/OpenAI_CodeAgent-action + with: + baseURL: https://api.deepseek.com + apiKey: ${{ secrets.API_KEY }} + fileOverWrite: true + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.MyPATToken }} # 使用 GitHub 提供的 token + branch: auto-pr-branch # 新分支名称 + base: main # 目标分支 + title: 'Automated PR: Update generated files' + body: 'This is an automated pull request created by GitHub Actions.' + commit-message: 'Auto-generated changes' + push-to-fork: SamYuan1990/kepler + sign-commits: true + labels: automated # 可选:为 PR 添加标签 From 9c8984bface3cc285ef7b339734aa4ce907b4e2a Mon Sep 17 00:00:00 2001 From: Sam Yuan Date: Wed, 15 Jan 2025 10:58:29 +0800 Subject: [PATCH 3/9] fix up Signed-off-by: Sam Yuan --- .github/workflows/dispatch.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml index 173d065b47..20f2036a82 100644 --- a/.github/workflows/dispatch.yml +++ b/.github/workflows/dispatch.yml @@ -16,6 +16,8 @@ jobs: - name: Checkout id: checkout uses: actions/checkout@v4 + with: + repository: sustainable-computing-io/kepler - name: Test Local Action id: test-action From 9a94dbbe5b06336756b836dd3b9bc811eed9d5a5 Mon Sep 17 00:00:00 2001 From: Sam Yuan Date: Wed, 15 Jan 2025 11:01:26 +0800 Subject: [PATCH 4/9] prepare for Tasks.json Signed-off-by: Sam Yuan --- Tasks.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Tasks.json diff --git a/Tasks.json b/Tasks.json new file mode 100644 index 0000000000..2aa516fa3b --- /dev/null +++ b/Tasks.json @@ -0,0 +1,20 @@ +{ + "tasks": [ + { + "id": "my_example_task_for_golang", + "inputFilePath": "./cmd/exporter/exporter.go", + "inputFileProcessMethod": "by_function", + "prompt": "Please help me create unit test file for this function with ginkgo framework, here is the function:", + "outputProcessMethod": "regex_match", + "outputFilePath": "./cmd/exporter/exporter{{index}}_test.go" + }, + { + "id": "my_example_task_for_golang_test", + "inputFilePath": "./pkg/collector/metric_collector_test.go", + "inputFileProcessMethod": "by_function", + "prompt": "Please help me create benchmark test content for this function, here is the content:", + "outputProcessMethod": "regex_match", + "outputFilePath": "./pkg/collector/metric_collector{{index}}_test.go" + } + ] +} From 4b8f8b21464c8592231344a04bbe07d5c942d3c1 Mon Sep 17 00:00:00 2001 From: Sam Yuan Date: Wed, 15 Jan 2025 11:25:34 +0800 Subject: [PATCH 5/9] fix up Signed-off-by: Sam Yuan --- .github/workflows/dispatch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml index 20f2036a82..b0c6110a21 100644 --- a/.github/workflows/dispatch.yml +++ b/.github/workflows/dispatch.yml @@ -21,7 +21,7 @@ jobs: - name: Test Local Action id: test-action - uses: SamYuan1990/OpenAI_CodeAgent-action + uses: SamYuan1990/OpenAI_CodeAgent-action@main with: baseURL: https://api.deepseek.com apiKey: ${{ secrets.API_KEY }} From e896da5b73290cda05f0cd65ece3b916b5636277 Mon Sep 17 00:00:00 2001 From: Sam Yuan Date: Wed, 15 Jan 2025 11:34:15 +0800 Subject: [PATCH 6/9] fix up Signed-off-by: Sam Yuan --- .github/workflows/dispatch.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml index b0c6110a21..ccb31522fd 100644 --- a/.github/workflows/dispatch.yml +++ b/.github/workflows/dispatch.yml @@ -16,8 +16,6 @@ jobs: - name: Checkout id: checkout uses: actions/checkout@v4 - with: - repository: sustainable-computing-io/kepler - name: Test Local Action id: test-action From fbd97a3c1ef8dac9a40b8956fe5f0e99f3c01783 Mon Sep 17 00:00:00 2001 From: Sam Yuan Date: Wed, 15 Jan 2025 11:37:06 +0800 Subject: [PATCH 7/9] fix up Signed-off-by: Sam Yuan --- .github/workflows/dispatch.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml index ccb31522fd..5328944049 100644 --- a/.github/workflows/dispatch.yml +++ b/.github/workflows/dispatch.yml @@ -34,6 +34,5 @@ jobs: title: 'Automated PR: Update generated files' body: 'This is an automated pull request created by GitHub Actions.' commit-message: 'Auto-generated changes' - push-to-fork: SamYuan1990/kepler sign-commits: true labels: automated # 可选:为 PR 添加标签 From d3664a5e47b2c9754b9e906a4d51d44d7668dcb6 Mon Sep 17 00:00:00 2001 From: SamYuan1990 Date: Wed, 15 Jan 2025 03:39:07 +0000 Subject: [PATCH 8/9] Auto-generated changes --- cmd/exporter/exporter0_test.go | 74 ++++++++++++++++++++++++++++++++++ cmd/exporter/exporter1_test.go | 36 +++++++++++++++++ cmd/exporter/exporter2_test.go | 73 +++++++++++++++++++++++++++++++++ cmd/exporter/exporter3_test.go | 64 +++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 cmd/exporter/exporter0_test.go create mode 100644 cmd/exporter/exporter1_test.go create mode 100644 cmd/exporter/exporter2_test.go create mode 100644 cmd/exporter/exporter3_test.go diff --git a/cmd/exporter/exporter0_test.go b/cmd/exporter/exporter0_test.go new file mode 100644 index 0000000000..8c04b6b98c --- /dev/null +++ b/cmd/exporter/exporter0_test.go @@ -0,0 +1,74 @@ +package your_package_name + +import ( + "flag" + "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = ginkgo.Describe("AppConfig", func() { + var cfg *AppConfig + + ginkgo.BeforeEach(func() { + // Reset the command-line flags before each test + flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError) + cfg = newAppConfig() + }) + + ginkgo.It("should initialize with default values", func() { + Expect(cfg.BaseDir).To(Equal(config.BaseDir)) + Expect(cfg.Address).To(Equal("0.0.0.0:8888")) + Expect(cfg.MetricsPath).To(Equal("/metrics")) + Expect(cfg.EnableGPU).To(BeFalse()) + Expect(cfg.EnableEBPFCgroupID).To(BeTrue()) + Expect(cfg.ExposeHardwareCounterMetrics).To(BeTrue()) + Expect(cfg.EnableMSR).To(BeFalse()) + Expect(cfg.Kubeconfig).To(BeEmpty()) + Expect(cfg.ApiserverEnabled).To(BeTrue()) + Expect(cfg.RedfishCredFilePath).To(BeEmpty()) + Expect(cfg.ExposeEstimatedIdlePower).To(BeFalse()) + Expect(cfg.MachineSpecFilePath).To(BeEmpty()) + Expect(cfg.DisablePowerMeter).To(BeFalse()) + Expect(cfg.TLSFilePath).To(BeEmpty()) + }) + + ginkgo.It("should override default values with command-line flags", func() { + // Set command-line flags + flag.Set("config-dir", "/custom/config/dir") + flag.Set("address", "127.0.0.1:8080") + flag.Set("metrics-path", "/custom/metrics") + flag.Set("enable-gpu", "true") + flag.Set("enable-cgroup-id", "false") + flag.Set("expose-hardware-counter-metrics", "false") + flag.Set("enable-msr", "true") + flag.Set("kubeconfig", "/custom/kubeconfig") + flag.Set("apiserver", "false") + flag.Set("redfish-cred-file-path", "/custom/redfish/cred") + flag.Set("expose-estimated-idle-power", "true") + flag.Set("machine-spec", "/custom/machine/spec") + flag.Set("disable-power-meter", "true") + flag.Set("web.config.file", "/custom/tls/config") + + // Parse the flags + flag.Parse() + + // Reinitialize the config with the new flag values + cfg = newAppConfig() + + // Verify that the values are overridden + Expect(cfg.BaseDir).To(Equal("/custom/config/dir")) + Expect(cfg.Address).To(Equal("127.0.0.1:8080")) + Expect(cfg.MetricsPath).To(Equal("/custom/metrics")) + Expect(cfg.EnableGPU).To(BeTrue()) + Expect(cfg.EnableEBPFCgroupID).To(BeFalse()) + Expect(cfg.ExposeHardwareCounterMetrics).To(BeFalse()) + Expect(cfg.EnableMSR).To(BeTrue()) + Expect(cfg.Kubeconfig).To(Equal("/custom/kubeconfig")) + Expect(cfg.ApiserverEnabled).To(BeFalse()) + Expect(cfg.RedfishCredFilePath).To(Equal("/custom/redfish/cred")) + Expect(cfg.ExposeEstimatedIdlePower).To(BeTrue()) + Expect(cfg.MachineSpecFilePath).To(Equal("/custom/machine/spec")) + Expect(cfg.DisablePowerMeter).To(BeTrue()) + Expect(cfg.TLSFilePath).To(Equal("/custom/tls/config")) + }) +}) \ No newline at end of file diff --git a/cmd/exporter/exporter1_test.go b/cmd/exporter/exporter1_test.go new file mode 100644 index 0000000000..c3ea102736 --- /dev/null +++ b/cmd/exporter/exporter1_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "net/http" + "net/http/httptest" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("HealthProbe", func() { + var ( + w *httptest.ResponseRecorder + req *http.Request + ) + + BeforeEach(func() { + // Initialize the response recorder and request before each test + w = httptest.NewRecorder() + req = httptest.NewRequest("GET", "/health", nil) + }) + + Context("when the health probe is called", func() { + It("should return HTTP status OK", func() { + healthProbe(w, req) + + Expect(w.Code).To(Equal(http.StatusOK)) + }) + + It("should return 'ok' in the response body", func() { + healthProbe(w, req) + + Expect(w.Body.String()).To(Equal("ok")) + }) + }) +}) \ No newline at end of file diff --git a/cmd/exporter/exporter2_test.go b/cmd/exporter/exporter2_test.go new file mode 100644 index 0000000000..ecd5911846 --- /dev/null +++ b/cmd/exporter/exporter2_test.go @@ -0,0 +1,73 @@ +package main + +import ( + "flag" + "os" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/klog/v2" +) + +var _ = Describe("Main Function", func() { + var ( + originalArgs []string + ) + + BeforeEach(func() { + // Save the original command-line arguments + originalArgs = os.Args + + // Reset the flag command-line arguments + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) + + // Initialize klog flags + klog.InitFlags(nil) + }) + + AfterEach(func() { + // Restore the original command-line arguments + os.Args = originalArgs + }) + + Context("when the config initialization fails", func() { + It("should log a fatal error and exit", func() { + // Mock the command-line arguments to simulate a failure scenario + os.Args = []string{"cmd", "-base-dir=/invalid/path"} + + // Redirect klog output to a buffer to capture the log messages + klog.SetOutput(GinkgoWriter) + + // Use a defer to recover from the fatal log and prevent the test from exiting + defer func() { + if r := recover(); r != nil { + Expect(r).To(ContainSubstring("Failed to initialize config")) + } + }() + + // Call the main function + main() + }) + }) + + Context("when the config initialization succeeds", func() { + It("should initialize the config without errors", func() { + // Mock the command-line arguments to simulate a success scenario + os.Args = []string{"cmd", "-base-dir=/valid/path"} + + // Redirect klog output to a buffer to capture the log messages + klog.SetOutput(GinkgoWriter) + + // Use a defer to recover from the fatal log and prevent the test from exiting + defer func() { + if r := recover(); r != nil { + Fail("Unexpected fatal log") + } + }() + + // Call the main function + main() + }) + }) +}) \ No newline at end of file diff --git a/cmd/exporter/exporter3_test.go b/cmd/exporter/exporter3_test.go new file mode 100644 index 0000000000..bb10125cd1 --- /dev/null +++ b/cmd/exporter/exporter3_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("RootHandler", func() { + var ( + metricPathConfig string + handler http.HandlerFunc + recorder *httptest.ResponseRecorder + request *http.Request + ) + + BeforeEach(func() { + metricPathConfig = "/metrics" + handler = rootHandler(metricPathConfig) + recorder = httptest.NewRecorder() + request = httptest.NewRequest("GET", "/", nil) + }) + + Context("when the root endpoint is accessed", func() { + It("should return a valid HTML response with the correct metric path", func() { + handler.ServeHTTP(recorder, request) + + Expect(recorder.Code).To(Equal(http.StatusOK)) + Expect(recorder.Body.String()).To(ContainSubstring("Energy Stats Exporter")) + Expect(recorder.Body.String()).To(ContainSubstring("

Energy Stats Exporter

")) + Expect(recorder.Body.String()).To(ContainSubstring(`Metrics`)) + }) + }) + + Context("when the response writing fails", func() { + It("should log an error", func() { + // Mock the response writer to simulate a write failure + failingRecorder := &FailingResponseRecorder{httptest.NewRecorder()} + handler.ServeHTTP(failingRecorder, request) + + // Here you would typically check if the error was logged. + // Since klog is used, you might need to capture logs or mock klog for this test. + // This is a placeholder to indicate where you would check for the error log. + Expect(failingRecorder.Failed).To(BeTrue()) + }) + }) +}) + +// FailingResponseRecorder is a custom ResponseRecorder that fails on Write +type FailingResponseRecorder struct { + *httptest.ResponseRecorder + Failed bool +} + +func (r *FailingResponseRecorder) Write(b []byte) (int, error) { + if strings.Contains(string(b), "Metrics") { + r.Failed = true + return 0, fmt.Errorf("simulated write failure") + } + return r.ResponseRecorder.Write(b) +} \ No newline at end of file From ce16f72a76f9f4ef306a828d0d81c691af43f494 Mon Sep 17 00:00:00 2001 From: Sam Yuan Date: Wed, 15 Jan 2025 13:54:28 +0800 Subject: [PATCH 9/9] add test case in cmd package and benchmark testing for collector package Signed-off-by: Sam Yuan --- cmd/exporter/exporter0_test.go | 74 ------------ cmd/exporter/exporter1_test.go | 36 ------ cmd/exporter/exporter2_test.go | 73 ------------ cmd/exporter/exporter3_test.go | 64 ---------- cmd/exporter/exporter_suite_test.go | 13 ++ cmd/exporter/exporter_test.go | 159 +++++++++++++++++++++++++ pkg/collector/metric_collector_test.go | 22 ++++ 7 files changed, 194 insertions(+), 247 deletions(-) delete mode 100644 cmd/exporter/exporter0_test.go delete mode 100644 cmd/exporter/exporter1_test.go delete mode 100644 cmd/exporter/exporter2_test.go delete mode 100644 cmd/exporter/exporter3_test.go create mode 100644 cmd/exporter/exporter_suite_test.go create mode 100644 cmd/exporter/exporter_test.go diff --git a/cmd/exporter/exporter0_test.go b/cmd/exporter/exporter0_test.go deleted file mode 100644 index 8c04b6b98c..0000000000 --- a/cmd/exporter/exporter0_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package your_package_name - -import ( - "flag" - "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = ginkgo.Describe("AppConfig", func() { - var cfg *AppConfig - - ginkgo.BeforeEach(func() { - // Reset the command-line flags before each test - flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError) - cfg = newAppConfig() - }) - - ginkgo.It("should initialize with default values", func() { - Expect(cfg.BaseDir).To(Equal(config.BaseDir)) - Expect(cfg.Address).To(Equal("0.0.0.0:8888")) - Expect(cfg.MetricsPath).To(Equal("/metrics")) - Expect(cfg.EnableGPU).To(BeFalse()) - Expect(cfg.EnableEBPFCgroupID).To(BeTrue()) - Expect(cfg.ExposeHardwareCounterMetrics).To(BeTrue()) - Expect(cfg.EnableMSR).To(BeFalse()) - Expect(cfg.Kubeconfig).To(BeEmpty()) - Expect(cfg.ApiserverEnabled).To(BeTrue()) - Expect(cfg.RedfishCredFilePath).To(BeEmpty()) - Expect(cfg.ExposeEstimatedIdlePower).To(BeFalse()) - Expect(cfg.MachineSpecFilePath).To(BeEmpty()) - Expect(cfg.DisablePowerMeter).To(BeFalse()) - Expect(cfg.TLSFilePath).To(BeEmpty()) - }) - - ginkgo.It("should override default values with command-line flags", func() { - // Set command-line flags - flag.Set("config-dir", "/custom/config/dir") - flag.Set("address", "127.0.0.1:8080") - flag.Set("metrics-path", "/custom/metrics") - flag.Set("enable-gpu", "true") - flag.Set("enable-cgroup-id", "false") - flag.Set("expose-hardware-counter-metrics", "false") - flag.Set("enable-msr", "true") - flag.Set("kubeconfig", "/custom/kubeconfig") - flag.Set("apiserver", "false") - flag.Set("redfish-cred-file-path", "/custom/redfish/cred") - flag.Set("expose-estimated-idle-power", "true") - flag.Set("machine-spec", "/custom/machine/spec") - flag.Set("disable-power-meter", "true") - flag.Set("web.config.file", "/custom/tls/config") - - // Parse the flags - flag.Parse() - - // Reinitialize the config with the new flag values - cfg = newAppConfig() - - // Verify that the values are overridden - Expect(cfg.BaseDir).To(Equal("/custom/config/dir")) - Expect(cfg.Address).To(Equal("127.0.0.1:8080")) - Expect(cfg.MetricsPath).To(Equal("/custom/metrics")) - Expect(cfg.EnableGPU).To(BeTrue()) - Expect(cfg.EnableEBPFCgroupID).To(BeFalse()) - Expect(cfg.ExposeHardwareCounterMetrics).To(BeFalse()) - Expect(cfg.EnableMSR).To(BeTrue()) - Expect(cfg.Kubeconfig).To(Equal("/custom/kubeconfig")) - Expect(cfg.ApiserverEnabled).To(BeFalse()) - Expect(cfg.RedfishCredFilePath).To(Equal("/custom/redfish/cred")) - Expect(cfg.ExposeEstimatedIdlePower).To(BeTrue()) - Expect(cfg.MachineSpecFilePath).To(Equal("/custom/machine/spec")) - Expect(cfg.DisablePowerMeter).To(BeTrue()) - Expect(cfg.TLSFilePath).To(Equal("/custom/tls/config")) - }) -}) \ No newline at end of file diff --git a/cmd/exporter/exporter1_test.go b/cmd/exporter/exporter1_test.go deleted file mode 100644 index c3ea102736..0000000000 --- a/cmd/exporter/exporter1_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "net/http" - "net/http/httptest" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("HealthProbe", func() { - var ( - w *httptest.ResponseRecorder - req *http.Request - ) - - BeforeEach(func() { - // Initialize the response recorder and request before each test - w = httptest.NewRecorder() - req = httptest.NewRequest("GET", "/health", nil) - }) - - Context("when the health probe is called", func() { - It("should return HTTP status OK", func() { - healthProbe(w, req) - - Expect(w.Code).To(Equal(http.StatusOK)) - }) - - It("should return 'ok' in the response body", func() { - healthProbe(w, req) - - Expect(w.Body.String()).To(Equal("ok")) - }) - }) -}) \ No newline at end of file diff --git a/cmd/exporter/exporter2_test.go b/cmd/exporter/exporter2_test.go deleted file mode 100644 index ecd5911846..0000000000 --- a/cmd/exporter/exporter2_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "flag" - "os" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/klog/v2" -) - -var _ = Describe("Main Function", func() { - var ( - originalArgs []string - ) - - BeforeEach(func() { - // Save the original command-line arguments - originalArgs = os.Args - - // Reset the flag command-line arguments - flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) - - // Initialize klog flags - klog.InitFlags(nil) - }) - - AfterEach(func() { - // Restore the original command-line arguments - os.Args = originalArgs - }) - - Context("when the config initialization fails", func() { - It("should log a fatal error and exit", func() { - // Mock the command-line arguments to simulate a failure scenario - os.Args = []string{"cmd", "-base-dir=/invalid/path"} - - // Redirect klog output to a buffer to capture the log messages - klog.SetOutput(GinkgoWriter) - - // Use a defer to recover from the fatal log and prevent the test from exiting - defer func() { - if r := recover(); r != nil { - Expect(r).To(ContainSubstring("Failed to initialize config")) - } - }() - - // Call the main function - main() - }) - }) - - Context("when the config initialization succeeds", func() { - It("should initialize the config without errors", func() { - // Mock the command-line arguments to simulate a success scenario - os.Args = []string{"cmd", "-base-dir=/valid/path"} - - // Redirect klog output to a buffer to capture the log messages - klog.SetOutput(GinkgoWriter) - - // Use a defer to recover from the fatal log and prevent the test from exiting - defer func() { - if r := recover(); r != nil { - Fail("Unexpected fatal log") - } - }() - - // Call the main function - main() - }) - }) -}) \ No newline at end of file diff --git a/cmd/exporter/exporter3_test.go b/cmd/exporter/exporter3_test.go deleted file mode 100644 index bb10125cd1..0000000000 --- a/cmd/exporter/exporter3_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "net/http" - "net/http/httptest" - "strings" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("RootHandler", func() { - var ( - metricPathConfig string - handler http.HandlerFunc - recorder *httptest.ResponseRecorder - request *http.Request - ) - - BeforeEach(func() { - metricPathConfig = "/metrics" - handler = rootHandler(metricPathConfig) - recorder = httptest.NewRecorder() - request = httptest.NewRequest("GET", "/", nil) - }) - - Context("when the root endpoint is accessed", func() { - It("should return a valid HTML response with the correct metric path", func() { - handler.ServeHTTP(recorder, request) - - Expect(recorder.Code).To(Equal(http.StatusOK)) - Expect(recorder.Body.String()).To(ContainSubstring("Energy Stats Exporter")) - Expect(recorder.Body.String()).To(ContainSubstring("

Energy Stats Exporter

")) - Expect(recorder.Body.String()).To(ContainSubstring(`Metrics`)) - }) - }) - - Context("when the response writing fails", func() { - It("should log an error", func() { - // Mock the response writer to simulate a write failure - failingRecorder := &FailingResponseRecorder{httptest.NewRecorder()} - handler.ServeHTTP(failingRecorder, request) - - // Here you would typically check if the error was logged. - // Since klog is used, you might need to capture logs or mock klog for this test. - // This is a placeholder to indicate where you would check for the error log. - Expect(failingRecorder.Failed).To(BeTrue()) - }) - }) -}) - -// FailingResponseRecorder is a custom ResponseRecorder that fails on Write -type FailingResponseRecorder struct { - *httptest.ResponseRecorder - Failed bool -} - -func (r *FailingResponseRecorder) Write(b []byte) (int, error) { - if strings.Contains(string(b), "Metrics") { - r.Failed = true - return 0, fmt.Errorf("simulated write failure") - } - return r.ResponseRecorder.Write(b) -} \ No newline at end of file diff --git a/cmd/exporter/exporter_suite_test.go b/cmd/exporter/exporter_suite_test.go new file mode 100644 index 0000000000..6b8b879998 --- /dev/null +++ b/cmd/exporter/exporter_suite_test.go @@ -0,0 +1,13 @@ +package main + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestExporter(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Exporter Suite") +} diff --git a/cmd/exporter/exporter_test.go b/cmd/exporter/exporter_test.go new file mode 100644 index 0000000000..7bf65425b3 --- /dev/null +++ b/cmd/exporter/exporter_test.go @@ -0,0 +1,159 @@ +package main + +import ( + "flag" + "fmt" + "net/http" + "net/http/httptest" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/sustainable-computing-io/kepler/pkg/config" +) + +var _ = Describe("AppConfig", func() { + var cfg *AppConfig + + BeforeEach(func() { + cfg = newAppConfig() + // Reset the command-line flags before each test + flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError) + }) + + It("should initialize with default values", func() { + Expect(cfg.BaseDir).To(Equal(config.BaseDir)) + Expect(cfg.Address).To(Equal("0.0.0.0:8888")) + Expect(cfg.MetricsPath).To(Equal("/metrics")) + Expect(cfg.EnableGPU).To(BeFalse()) + Expect(cfg.EnableEBPFCgroupID).To(BeTrue()) + Expect(cfg.ExposeHardwareCounterMetrics).To(BeTrue()) + Expect(cfg.EnableMSR).To(BeFalse()) + Expect(cfg.Kubeconfig).To(BeEmpty()) + Expect(cfg.ApiserverEnabled).To(BeTrue()) + Expect(cfg.RedfishCredFilePath).To(BeEmpty()) + Expect(cfg.ExposeEstimatedIdlePower).To(BeFalse()) + Expect(cfg.MachineSpecFilePath).To(BeEmpty()) + Expect(cfg.DisablePowerMeter).To(BeFalse()) + Expect(cfg.TLSFilePath).To(BeEmpty()) + }) + + It("should override default values with command-line flags", func() { + cfg = newAppConfig() + // Set command-line flags + flag.Set("config-dir", "/custom/config/dir") + flag.Set("address", "127.0.0.1:8080") + flag.Set("metrics-path", "/custom/metrics") + flag.Set("enable-gpu", "true") + flag.Set("enable-cgroup-id", "false") + flag.Set("expose-hardware-counter-metrics", "false") + flag.Set("enable-msr", "true") + flag.Set("kubeconfig", "/custom/kubeconfig") + flag.Set("apiserver", "false") + flag.Set("redfish-cred-file-path", "/custom/redfish/cred") + flag.Set("expose-estimated-idle-power", "true") + flag.Set("machine-spec", "/custom/machine/spec") + flag.Set("disable-power-meter", "true") + flag.Set("web.config.file", "/custom/tls/config") + + // Parse the flags + flag.Parse() + + // Verify that the values are overridden + Expect(cfg.BaseDir).To(Equal("/custom/config/dir")) + Expect(cfg.Address).To(Equal("127.0.0.1:8080")) + Expect(cfg.MetricsPath).To(Equal("/custom/metrics")) + Expect(cfg.EnableGPU).To(BeTrue()) + Expect(cfg.EnableEBPFCgroupID).To(BeFalse()) + Expect(cfg.ExposeHardwareCounterMetrics).To(BeFalse()) + Expect(cfg.EnableMSR).To(BeTrue()) + Expect(cfg.Kubeconfig).To(Equal("/custom/kubeconfig")) + Expect(cfg.ApiserverEnabled).To(BeFalse()) + Expect(cfg.RedfishCredFilePath).To(Equal("/custom/redfish/cred")) + Expect(cfg.ExposeEstimatedIdlePower).To(BeTrue()) + Expect(cfg.MachineSpecFilePath).To(Equal("/custom/machine/spec")) + Expect(cfg.DisablePowerMeter).To(BeTrue()) + Expect(cfg.TLSFilePath).To(Equal("/custom/tls/config")) + }) +}) + +var _ = Describe("HealthProbe", func() { + var ( + w *httptest.ResponseRecorder + req *http.Request + ) + + BeforeEach(func() { + // Initialize the response recorder and request before each test + w = httptest.NewRecorder() + req = httptest.NewRequest("GET", "/health", nil) + }) + + Context("when the health probe is called", func() { + It("should return HTTP status OK", func() { + healthProbe(w, req) + + Expect(w.Code).To(Equal(http.StatusOK)) + }) + + It("should return 'ok' in the response body", func() { + healthProbe(w, req) + + Expect(w.Body.String()).To(Equal("ok")) + }) + }) +}) + +var _ = Describe("RootHandler", func() { + var ( + metricPathConfig string + handler http.HandlerFunc + recorder *httptest.ResponseRecorder + request *http.Request + ) + + BeforeEach(func() { + metricPathConfig = "/metrics" + handler = rootHandler(metricPathConfig) + recorder = httptest.NewRecorder() + request = httptest.NewRequest("GET", "/", nil) + }) + + Context("when the root endpoint is accessed", func() { + It("should return a valid HTML response with the correct metric path", func() { + handler.ServeHTTP(recorder, request) + + Expect(recorder.Code).To(Equal(http.StatusOK)) + Expect(recorder.Body.String()).To(ContainSubstring("Energy Stats Exporter")) + Expect(recorder.Body.String()).To(ContainSubstring("

Energy Stats Exporter

")) + Expect(recorder.Body.String()).To(ContainSubstring(`Metrics`)) + }) + }) + + Context("when the response writing fails", func() { + It("should log an error", func() { + // Mock the response writer to simulate a write failure + failingRecorder := &FailingResponseRecorder{httptest.NewRecorder(), false} + handler.ServeHTTP(failingRecorder, request) + + // Here you would typically check if the error was logged. + // Since klog is used, you might need to capture logs or mock klog for this test. + // This is a placeholder to indicate where you would check for the error log. + Expect(failingRecorder.Failed).To(BeTrue()) + }) + }) +}) + +// FailingResponseRecorder is a custom ResponseRecorder that fails on Write +type FailingResponseRecorder struct { + *httptest.ResponseRecorder + Failed bool +} + +func (r *FailingResponseRecorder) Write(b []byte) (int, error) { + if strings.Contains(string(b), "Metrics") { + r.Failed = true + return 0, fmt.Errorf("simulated write failure") + } + return r.ResponseRecorder.Write(b) +} diff --git a/pkg/collector/metric_collector_test.go b/pkg/collector/metric_collector_test.go index 00c069ff80..01aeaadf0e 100644 --- a/pkg/collector/metric_collector_test.go +++ b/pkg/collector/metric_collector_test.go @@ -1,6 +1,8 @@ package collector import ( + "testing" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -74,3 +76,23 @@ var _ = Describe("Test Collector Unit", func() { }) }) + +func BenchmarkHandleInactiveContainers(b *testing.B) { + // Initialize the mock exporter and collector + config.Initialize(".") + bpfExporter := bpf.NewMockExporter(bpf.DefaultSupportedMetrics()) + metricCollector := newMockCollector(bpfExporter) + + // Create a map of found containers + foundContainer := make(map[string]bool) + foundContainer["container1"] = true + foundContainer["container2"] = true + + // Reset the timer to exclude setup time from the benchmark + b.ResetTimer() + + // Run the benchmark + for i := 0; i < b.N; i++ { + metricCollector.handleInactiveContainers(foundContainer) + } +}