Skip to content

Commit a4d5090

Browse files
authored
Merge branch 'master' into add-python-abi3
2 parents 6595ee0 + 4cb7828 commit a4d5090

29 files changed

+8837
-848
lines changed

.gitattributes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ docs/brotli-comparison-study-2015-09-22.pdf export-ignore
3333
python !export-ignore
3434
python/** !export-ignore
3535

36+
# Add go bindings + tests
37+
go !export-ignore
38+
go/** !export-ignore
39+
3640
# Add more build files.
3741
scripts !export-ignore
3842
scripts/download_testdata.sh !export-ignore

.github/workflows/build_test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
python_version: "3.10"
107107
# TODO: investigate why win-builds can't run tests
108108
py_setuptools_cmd: build_ext
109-
os: windows-2019
109+
os: windows-2022
110110

111111
- name: maven
112112
build_system: maven
@@ -152,7 +152,7 @@ jobs:
152152
bazel_project: .
153153
os: windows-latest
154154

155-
# TODO: use single dll on windows, otherwise it fails to link
155+
# TODO(eustas): restore when go is fixed
156156
#- name: bazel-win:go
157157
# build_system: bazel
158158
# bazel_project: go

go/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# Description:
2+
# brotli is a pure Go implementation of Brotli decoder.
23
# cbrotli is a CGo wrapper for Brotli, a generic-purpose lossless compression algorithm.

go/brotli/BUILD.bazel

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
licenses(["notice"]) # MIT
6+
7+
go_library(
8+
name = "brotli",
9+
srcs = [
10+
"decode.go",
11+
"reader.go",
12+
],
13+
importpath = "github.com/google/brotli/go/brotli",
14+
)
15+
16+
go_test(
17+
name = "brotli_test",
18+
size = "small",
19+
srcs = ["brotli_test.go"],
20+
deps = [
21+
":brotli",
22+
"//cbrotli",
23+
],
24+
)
25+
26+
go_test(
27+
name = "synth_test",
28+
size = "small",
29+
srcs = ["synth_test.go"],
30+
deps = [":brotli"],
31+
)

go/brotli/brotli_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright 2025 Google Inc. All Rights Reserved.
2+
//
3+
// Distributed under MIT license.
4+
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
5+
6+
package brotli_test
7+
8+
import (
9+
"bytes"
10+
"fmt"
11+
"io"
12+
"math/rand"
13+
"testing"
14+
15+
"github.com/google/brotli/go/brotli"
16+
"github.com/google/brotli/go/cbrotli"
17+
)
18+
19+
func TestReader(t *testing.T) {
20+
content := bytes.Repeat([]byte("hello world!"), 10000)
21+
encoded, _ := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: 5})
22+
r := brotli.NewReader(bytes.NewReader(encoded))
23+
var decodedOutput bytes.Buffer
24+
n, err := io.Copy(&decodedOutput, r)
25+
if err != nil {
26+
t.Fatalf("Copy(): n=%v, err=%v", n, err)
27+
}
28+
if err := r.Close(); err != nil {
29+
t.Errorf("Close(): %v", err)
30+
}
31+
if got := decodedOutput.Bytes(); !bytes.Equal(got, content) {
32+
t.Errorf(""+
33+
"Reader output:\n"+
34+
"%q\n"+
35+
"want:\n"+
36+
"<%d bytes>",
37+
got, len(content))
38+
}
39+
buf := make([]byte, 4)
40+
if _, err := r.Read(buf); err == nil {
41+
t.Errorf("Read-after-Close expected to return error")
42+
}
43+
}
44+
45+
func TestDecode(t *testing.T) {
46+
content := bytes.Repeat([]byte("hello world!"), 10000)
47+
encoded, _ := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: 5})
48+
decoded, err := brotli.Decode(encoded)
49+
if err != nil {
50+
t.Errorf("Decode: %v", err)
51+
}
52+
if !bytes.Equal(decoded, content) {
53+
t.Errorf(""+
54+
"Decode content:\n"+
55+
"%q\n"+
56+
"want:\n"+
57+
"<%d bytes>",
58+
decoded, len(content))
59+
}
60+
}
61+
62+
func TestDecodeFuzz(t *testing.T) {
63+
// Test that the decoder terminates with corrupted input.
64+
content := bytes.Repeat([]byte("hello world!"), 100)
65+
src := rand.NewSource(0)
66+
encoded, err := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: 5})
67+
if err != nil {
68+
t.Fatalf("Encode(<%d bytes>, _) = _, %s", len(content), err)
69+
}
70+
if len(encoded) == 0 {
71+
t.Fatalf("Encode(<%d bytes>, _) produced empty output", len(content))
72+
}
73+
for i := 0; i < 100; i++ {
74+
enc := append([]byte{}, encoded...)
75+
for j := 0; j < 5; j++ {
76+
enc[int(src.Int63())%len(enc)] = byte(src.Int63() % 256)
77+
}
78+
brotli.Decode(enc)
79+
}
80+
}
81+
82+
func TestDecodeTrailingData(t *testing.T) {
83+
content := bytes.Repeat([]byte("hello world!"), 100)
84+
encoded, _ := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: 5})
85+
_, err := brotli.Decode(append(encoded, 0))
86+
if err == nil {
87+
t.Errorf("Expected 'excessive input' error")
88+
}
89+
}
90+
91+
func TestEncodeDecode(t *testing.T) {
92+
for _, test := range []struct {
93+
data []byte
94+
repeats int
95+
}{
96+
{nil, 0},
97+
{[]byte("A"), 1},
98+
{[]byte("<html><body><H1>Hello world</H1></body></html>"), 10},
99+
{[]byte("<html><body><H1>Hello world</H1></body></html>"), 1000},
100+
} {
101+
t.Logf("case %q x %d", test.data, test.repeats)
102+
input := bytes.Repeat(test.data, test.repeats)
103+
encoded, err := cbrotli.Encode(input, cbrotli.WriterOptions{Quality: 5})
104+
if err != nil {
105+
t.Errorf("Encode: %v", err)
106+
}
107+
// Inputs are compressible, but may be too small to compress.
108+
if maxSize := len(input)/2 + 20; len(encoded) >= maxSize {
109+
t.Errorf(""+
110+
"Encode returned %d bytes, want <%d\n"+
111+
"Encoded=%q",
112+
len(encoded), maxSize, encoded)
113+
}
114+
decoded, err := brotli.Decode(encoded)
115+
if err != nil {
116+
t.Errorf("Decode: %v", err)
117+
}
118+
if !bytes.Equal(decoded, input) {
119+
var want string
120+
if len(input) > 320 {
121+
want = fmt.Sprintf("<%d bytes>", len(input))
122+
} else {
123+
want = fmt.Sprintf("%q", input)
124+
}
125+
t.Errorf(""+
126+
"Decode content:\n"+
127+
"%q\n"+
128+
"want:\n"+
129+
"%s",
130+
decoded, want)
131+
}
132+
}
133+
}
134+
135+
func TestEncodeDecodeWithDictionary(t *testing.T) {
136+
q := 5
137+
l := 4096
138+
139+
input := make([]byte, l)
140+
for i := 0; i < l; i++ {
141+
input[i] = byte(i*7 + i*i*5)
142+
}
143+
// use dictionary same as input
144+
pd := cbrotli.NewPreparedDictionary(input, cbrotli.DtRaw, q)
145+
defer pd.Close()
146+
147+
encoded, err := cbrotli.Encode(input, cbrotli.WriterOptions{Quality: q, Dictionary: pd})
148+
if err != nil {
149+
t.Errorf("Encode: %v", err)
150+
}
151+
limit := 20
152+
if len(encoded) > limit {
153+
t.Errorf("Output length exceeds expectations: %d > %d", len(encoded), limit)
154+
}
155+
156+
decoded, err := brotli.DecodeWithRawDictionary(encoded, input)
157+
if err != nil {
158+
t.Errorf("Decode: %v", err)
159+
}
160+
if !bytes.Equal(decoded, input) {
161+
var want string
162+
if len(input) > 320 {
163+
want = fmt.Sprintf("<%d bytes>", len(input))
164+
} else {
165+
want = fmt.Sprintf("%q", input)
166+
}
167+
t.Errorf(""+
168+
"Decode content:\n"+
169+
"%q\n"+
170+
"want:\n"+
171+
"%s",
172+
decoded, want)
173+
}
174+
}

0 commit comments

Comments
 (0)