Skip to content

Commit 56601c8

Browse files
committed
tests
1 parent 40a99c3 commit 56601c8

File tree

7 files changed

+482
-9
lines changed

7 files changed

+482
-9
lines changed

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: build
22

33
on:
44
push:
5-
branches:
5+
branches:
66
tags:
77
pull_request:
88

app/run.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package app
22

33
import (
44
"fmt"
5-
realOS "os"
65
"path/filepath"
76
"sync"
87
"time"
@@ -50,7 +49,11 @@ func calculateFileHeaderSize(path string) int64 {
5049
return getFileHeaderSize
5150
}
5251

53-
func writeFile(path string, data *[]byte, delimiter string, file *realOS.File, offset int64) (int64, error) {
52+
type fileWriter interface {
53+
WriteAt(b []byte, off int64) (n int, err error)
54+
}
55+
56+
func writeFile(path string, data *[]byte, delimiter string, file fileWriter, offset int64) (int64, error) {
5457
header := getFileHeader(path)
5558
n1, err := file.WriteAt([]byte(header), offset)
5659
if err != nil {
@@ -74,7 +77,9 @@ type processingResult struct {
7477
totalTokens int
7578
}
7679

77-
func process(paths []string, writeTree bool, output, delimiter string) (processingResult, error) {
80+
type estimateTokensFunc func(text, method string) (int, error)
81+
82+
func process(paths []string, writeTree bool, output, delimiter string, estimateTokens estimateTokensFunc) (processingResult, error) {
7883
currentOffset := int64(0)
7984
totalTokens := 0
8085

@@ -137,7 +142,7 @@ func process(paths []string, writeTree bool, output, delimiter string) (processi
137142
return
138143
}
139144
// estimate tokens
140-
tokens, err := EstimateTokens(string(data), "max")
145+
tokens, err := estimateTokens(string(data), "max")
141146
if err != nil {
142147
errChan <- err
143148
return
@@ -202,7 +207,7 @@ func Run(options RunOptions) error {
202207
}
203208
startTime := time.Now()
204209

205-
result, err := process(filePaths, !options.HideTree, options.Output, options.Delimiter)
210+
result, err := process(filePaths, !options.HideTree, options.Output, options.Delimiter, EstimateTokens)
206211
if err != nil {
207212
log.Errorf("error processing files: %v", err)
208213
return fmt.Errorf("error processing files: %v", err)

app/run_test.go

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
package app
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"github.com/MrZoidberg/contexify/pkg/os/mocks"
9+
)
10+
11+
func TestCalculateFileHeaderSize(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
path string
15+
expected int64
16+
}{
17+
{
18+
name: "Empty path",
19+
path: "",
20+
expected: int64(len(getFileHeader(""))),
21+
},
22+
{
23+
name: "Simple path",
24+
path: "test.go",
25+
expected: int64(len(getFileHeader("test.go"))),
26+
},
27+
{
28+
name: "Path with directory",
29+
path: "dir/test.go",
30+
expected: int64(len(getFileHeader("dir/test.go"))),
31+
},
32+
{
33+
name: "Complex path",
34+
path: "some/deep/nested/directory/structure/file.go",
35+
expected: int64(len(getFileHeader("some/deep/nested/directory/structure/file.go"))),
36+
},
37+
{
38+
name: "Path with backslashes",
39+
path: "folder\\subfolder\\file.txt",
40+
expected: int64(len(getFileHeader("folder\\subfolder\\file.txt"))),
41+
},
42+
{
43+
name: "Path with special characters",
44+
path: "test-file_name.txt!",
45+
expected: int64(len(getFileHeader("test-file_name.txt!"))),
46+
},
47+
}
48+
49+
for _, tt := range tests {
50+
t.Run(tt.name, func(t *testing.T) {
51+
got := calculateFileHeaderSize(tt.path)
52+
if got != tt.expected {
53+
t.Errorf("calculateFileHeaderSize(%q) = %d, want %d", tt.path, got, tt.expected)
54+
}
55+
})
56+
}
57+
}
58+
59+
func TestWriteFile(t *testing.T) {
60+
tests := []struct {
61+
name string
62+
path string
63+
data []byte
64+
delimiter string
65+
offset int64
66+
wantN int64
67+
wantErr bool
68+
writeErrs []error
69+
}{
70+
{
71+
name: "Simple write",
72+
path: "test.txt",
73+
data: []byte("test data"),
74+
delimiter: "\n",
75+
offset: 0,
76+
wantN: int64(len(getFileHeader("test.txt")) + len("test data") + 1),
77+
wantErr: false,
78+
},
79+
{
80+
name: "Write with offset",
81+
path: "test.txt",
82+
data: []byte("test data"),
83+
delimiter: "\n",
84+
offset: 100,
85+
wantN: int64(len(getFileHeader("test.txt")) + len("test data") + 1),
86+
wantErr: false,
87+
},
88+
{
89+
name: "Write without delimiter",
90+
path: "test.txt",
91+
data: []byte("test data"),
92+
delimiter: "",
93+
offset: 0,
94+
wantN: int64(len(getFileHeader("test.txt")) + len("test data")),
95+
wantErr: false,
96+
},
97+
{
98+
name: "Error on header write",
99+
path: "test.txt",
100+
data: []byte("test data"),
101+
delimiter: "\n",
102+
offset: 0,
103+
writeErrs: []error{fmt.Errorf("header write error")},
104+
wantErr: true,
105+
},
106+
{
107+
name: "Error on data write",
108+
path: "test.txt",
109+
data: []byte("test data"),
110+
delimiter: "\n",
111+
offset: 0,
112+
writeErrs: []error{nil, fmt.Errorf("data write error")},
113+
wantErr: true,
114+
},
115+
{
116+
name: "Error on delimiter write",
117+
path: "test.txt",
118+
data: []byte("test data"),
119+
delimiter: "\n",
120+
offset: 0,
121+
writeErrs: []error{nil, nil, fmt.Errorf("delimiter write error")},
122+
wantErr: true,
123+
},
124+
}
125+
126+
for _, tt := range tests {
127+
t.Run(tt.name, func(t *testing.T) {
128+
mockFile := &mocks.MockFile{}
129+
writeCallIndex := 0
130+
131+
mockFile.WriteAtFunc = func(b []byte, off int64) (int, error) {
132+
var err error
133+
if tt.writeErrs != nil && writeCallIndex < len(tt.writeErrs) {
134+
err = tt.writeErrs[writeCallIndex]
135+
}
136+
writeCallIndex++
137+
return len(b), err
138+
}
139+
140+
got, err := writeFile(tt.path, &tt.data, tt.delimiter, mockFile, tt.offset)
141+
if (err != nil) != tt.wantErr {
142+
t.Errorf("writeFile() error = %v, wantErr %v", err, tt.wantErr)
143+
return
144+
}
145+
if err == nil && got != tt.wantN {
146+
t.Errorf("writeFile() = %v, want %v", got, tt.wantN)
147+
}
148+
})
149+
}
150+
}
151+
152+
func TestProcess_Success(t *testing.T) {
153+
// Create a temporary directory for test files.
154+
tempDir, err := os.MkdirTemp("", "process_success")
155+
if err != nil {
156+
t.Fatalf("MkdirTemp error: %v", err)
157+
}
158+
defer os.RemoveAll(tempDir)
159+
160+
// Create two temporary files with known content.
161+
file1Path := tempDir + "/file1.txt"
162+
content1 := []byte("hello world")
163+
if err := os.WriteFile(file1Path, content1, 0644); err != nil {
164+
t.Fatalf("WriteFile error for file1: %v", err)
165+
}
166+
167+
file2Path := tempDir + "/file2.txt"
168+
content2 := []byte("Go testing!")
169+
if err := os.WriteFile(file2Path, content2, 0644); err != nil {
170+
t.Fatalf("WriteFile error for file2: %v", err)
171+
}
172+
173+
// Create a temporary output file.
174+
outputPath := tempDir + "/output.txt"
175+
// Ensure output file does not exist yet.
176+
_ = os.Remove(outputPath)
177+
178+
// Use a delimiter.
179+
delimiter := "\n"
180+
// Use writeTree as false to avoid relying on GenerateFileTree.
181+
paths := []string{file1Path, file2Path}
182+
183+
// Call process.
184+
estimateTokensFunc := func(text, method string) (int, error) {
185+
return 10, nil
186+
}
187+
result, err := process(paths, false, outputPath, delimiter, estimateTokensFunc)
188+
if err != nil {
189+
t.Fatalf("process error: %v", err)
190+
}
191+
192+
// Calculate expected totalSize.
193+
// For each file, expected bytes = file header size + file data length + delimiter length.
194+
var expectedSize int64
195+
for _, p := range paths {
196+
fi, err := os.Stat(p)
197+
if err != nil {
198+
t.Fatalf("Stat error for %s: %v", p, err)
199+
}
200+
expectedSize += fi.Size() + calculateFileHeaderSize(p) + int64(len(delimiter))
201+
}
202+
if result.totalSize != expectedSize {
203+
t.Errorf("process totalSize = %d, want %d", result.totalSize, expectedSize)
204+
}
205+
206+
// Optionally, verify that output file contains expected content.
207+
outData, err := os.ReadFile(outputPath)
208+
if err != nil {
209+
t.Fatalf("ReadFile error for output: %v", err)
210+
}
211+
// For each input file, output should contain its header, its data and the delimiter.
212+
for _, p := range paths {
213+
header := getFileHeader(p)
214+
data, err := os.ReadFile(p)
215+
if err != nil {
216+
t.Fatalf("ReadFile error for %s: %v", p, err)
217+
}
218+
expectedFragment := header + string(data) + delimiter
219+
if !contains(string(outData), expectedFragment) {
220+
t.Errorf("output file missing expected fragment for %s", p)
221+
}
222+
}
223+
}
224+
225+
func TestProcess_Error(t *testing.T) {
226+
// Use a path that does not exist.
227+
nonExistentPath := "nonexistent.txt"
228+
// Create a temporary file for output.
229+
tempOut, err := os.CreateTemp("", "process_error")
230+
if err != nil {
231+
t.Fatalf("CreateTemp error: %v", err)
232+
}
233+
outputPath := tempOut.Name()
234+
tempOut.Close()
235+
defer os.Remove(outputPath)
236+
237+
// Call process with a non-existent file.
238+
estimateTokensFunc := func(text, method string) (int, error) {
239+
return 10, nil
240+
}
241+
_, err = process([]string{nonExistentPath}, false, outputPath, "\n", estimateTokensFunc)
242+
if err == nil {
243+
t.Errorf("process error = nil, want an error due to nonexistent input file")
244+
}
245+
}
246+
247+
func contains(s, substr string) bool {
248+
return len(s) >= len(substr) && (s == substr || (len(s) > 0 && indexOf(s, substr) >= 0))
249+
}
250+
251+
func indexOf(s, substr string) int {
252+
for i := range s {
253+
if len(s[i:]) >= len(substr) && s[i:i+len(substr)] == substr {
254+
return i
255+
}
256+
}
257+
return -1
258+
}

app/tokenizer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ func EstimateTokens(text, method string) (int, error) {
3535
return 0, errors.New("invalid method. Use 'average', 'words', 'chars', 'max', or 'min'")
3636
}
3737

38-
return int(output), nil
38+
return int(math.Ceil(output)), nil
3939
}

0 commit comments

Comments
 (0)