11package integration
22
33import (
4+ "bufio"
45 "fmt"
6+ "maps"
57 "os"
68 "path/filepath"
9+ "slices"
710 "strconv"
811 "strings"
912 "testing"
@@ -14,43 +17,124 @@ import (
1417func TestIntegration (t * testing.T ) {
1518 projectName := strings .ToLower (t .Name ())
1619 outputDir := t .TempDir ()
17- local_exec_filename := "local_exec.txt"
18- exec_filename := "exec.txt"
19- run_filename := "run.txt"
20- sleep_for_seconds := 15
21- schedule_seconds := 3
20+ composeFile := "./test-run-exec/docker-compose.yml"
2221
23- t .Setenv ("COMPOSE_FILE" , "./test-run-exec/docker-compose.yml" )
22+ localExecFilename := "my-local-job.txt"
23+ execFilename := "my-exec-job.txt"
24+ runFilename := "my-run-job.txt"
25+
26+ sleepForSec := 10
27+ scheduleEverySec := 3
28+ expectedExecutions := (sleepForSec / scheduleEverySec )
29+
30+ if _ , err := os .Stat (composeFile ); os .IsNotExist (err ) {
31+ t .Fatalf ("Compose file %s not found" , composeFile )
32+ }
33+
34+ t .Setenv ("COMPOSE_FILE" , composeFile )
2435 t .Setenv ("COMPOSE_PROJECT_NAME" , projectName )
2536 t .Setenv ("OUTPUT_DIR" , outputDir )
26- t .Setenv ("SCHEDULE" , fmt .Sprintf ("@every %ds" , schedule_seconds ))
27- t .Setenv ("SLEEP_SEC" , strconv .Itoa (sleep_for_seconds ))
28- t .Setenv ("LOCAL_EXEC_OUTPUT_FILE" , local_exec_filename )
29- t .Setenv ("EXEC_OUTPUT_FILE" , exec_filename )
30- t .Setenv ("RUN_OUTPUT_FILE" , run_filename )
31-
32- t .Log ("Printing docker compose config" )
33- if err := sh .RunV ("docker" , "compose" , "config" ); err != nil {
34- t .Fatal (err )
35- }
37+ t .Setenv ("SCHEDULE" , fmt .Sprintf ("@every %ds" , scheduleEverySec ))
38+ t .Setenv ("SLEEP_FOR" , strconv .Itoa (sleepForSec ))
39+ t .Setenv ("LOCAL_EXEC_OUTPUT_FILE" , localExecFilename )
40+ t .Setenv ("EXEC_OUTPUT_FILE" , execFilename )
41+ t .Setenv ("RUN_OUTPUT_FILE" , runFilename )
3642
37- t .Log ("Running docker compose up" )
38- if err := sh .RunV ("docker" , "compose" , "up" , "--exit-code-from" , "sleep1" ); err != nil {
39- t .Error (err )
43+ for _ , command := range []string {"config" , "build" , "pull" } {
44+ t .Run ("docker compose " + command , func (t * testing.T ) {
45+ t .Logf ("Running docker compose %s" , command )
46+ if err := sh .RunV ("docker" , "compose" , command ); err != nil {
47+ t .Fatal (err )
48+ }
49+ })
4050 }
4151
42- for _ , file := range []string {local_exec_filename , exec_filename , run_filename } {
43- t .Run (file , func (t * testing.T ) {
52+ t .Run ("docker compose up" , func (t * testing.T ) {
53+ if err := sh .RunV ("docker" , "compose" , "up" , "--exit-code-from" , "sleep1" ); err != nil {
54+ t .Fatal (err )
55+ }
56+
57+ for _ , file := range []string {localExecFilename , execFilename , runFilename } {
4458 t .Log ("Checking for outputs in" , file )
45- data , err := os . ReadFile (filepath .Join (outputDir , file ))
59+ count , content , err := checkFile (filepath .Join (outputDir , file ))
4660 if err != nil {
4761 t .Error (err )
62+ continue
4863 }
4964
50- split := strings .Split (string (data ), "\n " )
51- if expectedLines := sleep_for_seconds / schedule_seconds ; len (split ) != expectedLines {
52- t .Errorf ("expected %d lines in %s, but got %d" , expectedLines , file , len (split ))
65+ if count != expectedExecutions {
66+ t .Errorf ("expected %d lines in %s, but got %d. File content:\n %s" , expectedExecutions , file , count , content )
5367 }
54- })
68+
69+ // check files generated by the `save-folder` setting
70+ resultFiles , err := checkSavedResults (file , outputDir )
71+ if err != nil {
72+ t .Error (err )
73+ }
74+
75+ if len (resultFiles ) != 3 {
76+ keys := slices .Sorted (maps .Keys (resultFiles ))
77+ t .Errorf ("expected 3 result files for %s, but got %d. Result files: %v" , file , len (resultFiles ), keys )
78+ }
79+
80+ for file , count := range resultFiles {
81+ if count != expectedExecutions {
82+ t .Errorf ("expected %d job execution files for %s, but got %d" , expectedExecutions , file , count )
83+ }
84+ }
85+ }
86+ })
87+ }
88+
89+ func checkSavedResults (fileName string , dir string ) (map [string ]int , error ) {
90+ jobName := strings .TrimSuffix (fileName , filepath .Ext (fileName ))
91+ files , err := os .ReadDir (dir )
92+ if err != nil {
93+ return nil , err
94+ }
95+
96+ // Look in the outpot directory and count the number of files (without the filename prefix)
97+ // Ofelia produces job executon results with the following naming convention:
98+ // - 20250419_182443_my-local-job.json
99+ // - 20250419_182443_my-local-job.stderr.log
100+ // - 20250419_182443_my-local-job.stdout.log
101+ //
102+ // Before counting, we will trim timestamp prefix.
103+
104+ counts := make (map [string ]int )
105+ for _ , file := range files {
106+ if strings .Contains (file .Name (), jobName ) {
107+ nameSplit := strings .Split (file .Name (), jobName )
108+ fileType := strings .TrimPrefix (file .Name (), nameSplit [0 ])
109+ counts [fileType ]++
110+ }
111+ }
112+
113+ // remove original file from counts
114+ delete (counts , fileName )
115+
116+ return counts , nil
117+ }
118+
119+ func checkFile (path string ) (int , string , error ) {
120+ f , err := os .Open (path )
121+ if err != nil {
122+ return 0 , "" , err
123+ }
124+ defer f .Close ()
125+
126+ scanner := bufio .NewScanner (f )
127+ content := strings.Builder {}
128+ count := 0
129+ for scanner .Scan () {
130+ content .Write (scanner .Bytes ())
131+ content .WriteByte ('\n' )
132+ count ++
133+ }
134+
135+ if err := scanner .Err (); err != nil {
136+ return 0 , "" , err
55137 }
138+
139+ return count , content .String (), nil
56140}
0 commit comments