@@ -4,10 +4,12 @@ import (
44 "bytes"
55 "github.com/speakeasy-api/jsonpath/pkg/jsonpath"
66 "github.com/speakeasy-api/openapi-overlay/pkg/loader"
7+ "github.com/speakeasy-api/openapi-overlay/pkg/overlay"
78 "github.com/stretchr/testify/assert"
89 "github.com/stretchr/testify/require"
910 "gopkg.in/yaml.v3"
1011 "os"
12+ "strconv"
1113 "testing"
1214)
1315
@@ -75,6 +77,195 @@ func TestApplyToStrict(t *testing.T) {
7577 assert .Len (t , warnings , 1 )
7678 assert .Equal (t , "update action (2 / 2) target=$.info.title: does nothing" , warnings [0 ])
7779 NodeMatchesFile (t , node , "testdata/openapi-strict-onechange.yaml" )
80+
81+ node , err = loader .LoadSpecification ("testdata/openapi.yaml" )
82+ require .NoError (t , err )
83+
84+ o , err = loader .LoadOverlay ("testdata/overlay.yaml" )
85+ require .NoError (t , err )
86+
87+ err = o .ApplyTo (node )
88+ assert .NoError (t , err )
89+
90+ NodeMatchesFile (t , node , "testdata/openapi-overlayed.yaml" )
91+
92+ }
93+
94+ func BenchmarkApplyToStrict (b * testing.B ) {
95+ openAPIBytes , err := os .ReadFile ("testdata/openapi.yaml" )
96+ require .NoError (b , err )
97+ overlayBytes , err := os .ReadFile ("testdata/overlay-zero-change.yaml" )
98+ require .NoError (b , err )
99+
100+ var specNode yaml.Node
101+ err = yaml .NewDecoder (bytes .NewReader (openAPIBytes )).Decode (& specNode )
102+ require .NoError (b , err )
103+
104+ // Load overlay from bytes
105+ var o overlay.Overlay
106+ err = yaml .NewDecoder (bytes .NewReader (overlayBytes )).Decode (& o )
107+ require .NoError (b , err )
108+
109+ // Apply overlay to spec
110+ for b .Loop () {
111+ _ , _ = o .ApplyToStrict (& specNode )
112+ }
113+ }
114+
115+ func BenchmarkApplyToStrictBySize (b * testing.B ) {
116+ // Read the base OpenAPI spec
117+ openAPIBytes , err := os .ReadFile ("testdata/openapi.yaml" )
118+ require .NoError (b , err )
119+
120+ // Read the overlay spec
121+ overlayBytes , err := os .ReadFile ("testdata/overlay-zero-change.yaml" )
122+ require .NoError (b , err )
123+
124+ // Decode the base spec
125+ var baseSpec yaml.Node
126+ err = yaml .NewDecoder (bytes .NewReader (openAPIBytes )).Decode (& baseSpec )
127+ require .NoError (b , err )
128+
129+ // Find the paths node and a path to duplicate
130+ pathsNode := findPathsNode (& baseSpec )
131+ require .NotNil (b , pathsNode )
132+
133+ // Get the first path item to use as template
134+ var templatePath * yaml.Node
135+ var templateKey string
136+ for i := 0 ; i < len (pathsNode .Content ); i += 2 {
137+ if pathsNode .Content [i ].Kind == yaml .ScalarNode && pathsNode .Content [i ].Value [0 ] == '/' {
138+ templateKey = pathsNode .Content [i ].Value
139+ templatePath = pathsNode .Content [i + 1 ]
140+ break
141+ }
142+ }
143+ require .NotNil (b , templatePath )
144+
145+ // Target sizes: 2KB, 20KB, 200KB, 2MB, 20MB
146+ targetSizes := []struct {
147+ size int
148+ name string
149+ }{
150+ {2 * 1024 , "2KB" },
151+ {20 * 1024 , "20KB" },
152+ {200 * 1024 , "200KB" },
153+ {2000 * 1024 , "2M" },
154+ }
155+
156+ // Calculate the base document size
157+ var baseBuf bytes.Buffer
158+ enc := yaml .NewEncoder (& baseBuf )
159+ err = enc .Encode (& baseSpec )
160+ require .NoError (b , err )
161+ baseSize := baseBuf .Len ()
162+
163+ // Calculate the size of a single path item by encoding it
164+ var pathBuf bytes.Buffer
165+ pathEnc := yaml .NewEncoder (& pathBuf )
166+ tempNode := & yaml.Node {
167+ Kind : yaml .MappingNode ,
168+ Content : []* yaml.Node {
169+ {Kind : yaml .ScalarNode , Value : templateKey + "-test" },
170+ cloneNode (templatePath ),
171+ },
172+ }
173+ err = pathEnc .Encode (tempNode )
174+ require .NoError (b , err )
175+ // Approximate size contribution of one path (accounting for YAML structure)
176+ pathItemSize := pathBuf .Len () - 10 // Subtract some overhead
177+
178+ for _ , target := range targetSizes {
179+ b .Run (target .name , func (b * testing.B ) {
180+ // Create a copy of the base spec
181+ specCopy := cloneNode (& baseSpec )
182+ pathsNodeCopy := findPathsNode (specCopy )
183+
184+ // Calculate how many paths we need to add
185+ bytesNeeded := target .size - baseSize
186+ pathsToAdd := 0
187+ if bytesNeeded > 0 {
188+ pathsToAdd = bytesNeeded / pathItemSize
189+ // Add a few extra to ensure we exceed the target
190+ pathsToAdd += 5
191+ }
192+
193+ // Add the calculated number of path duplicates
194+ for i := 0 ; i < pathsToAdd ; i ++ {
195+ newPathKey := yaml.Node {Kind : yaml .ScalarNode , Value : templateKey + "-duplicate-" + strconv .Itoa (i )}
196+ newPathValue := cloneNode (templatePath )
197+ pathsNodeCopy .Content = append (pathsNodeCopy .Content , & newPathKey , newPathValue )
198+ }
199+
200+ // Verify final size
201+ var finalBuf bytes.Buffer
202+ finalEnc := yaml .NewEncoder (& finalBuf )
203+ err = finalEnc .Encode (specCopy )
204+ require .NoError (b , err )
205+ actualSize := finalBuf .Len ()
206+ b .Logf ("OpenAPI size: %d bytes (target: %d, paths added: %d)" , actualSize , target .size , pathsToAdd )
207+
208+ // Load overlay
209+ var o overlay.Overlay
210+ err = yaml .NewDecoder (bytes .NewReader (overlayBytes )).Decode (& o )
211+ require .NoError (b , err )
212+
213+ specForTest := cloneNode (specCopy )
214+ // Run the benchmark
215+ b .ResetTimer ()
216+ for b .Loop () {
217+ _ , _ = o .ApplyToStrict (specForTest )
218+ }
219+ })
220+ }
221+ }
222+
223+ // Helper function to find the paths node in the OpenAPI spec
224+ func findPathsNode (node * yaml.Node ) * yaml.Node {
225+ if node .Kind == yaml .DocumentNode && len (node .Content ) > 0 {
226+ node = node .Content [0 ]
227+ }
228+
229+ if node .Kind != yaml .MappingNode {
230+ return nil
231+ }
232+
233+ for i := 0 ; i < len (node .Content ); i += 2 {
234+ if node .Content [i ].Value == "paths" {
235+ return node .Content [i + 1 ]
236+ }
237+ }
238+ return nil
239+ }
240+
241+ // Helper function to deep clone a YAML node
242+ func cloneNode (node * yaml.Node ) * yaml.Node {
243+ if node == nil {
244+ return nil
245+ }
246+
247+ clone := & yaml.Node {
248+ Kind : node .Kind ,
249+ Style : node .Style ,
250+ Tag : node .Tag ,
251+ Value : node .Value ,
252+ Anchor : node .Anchor ,
253+ Alias : node .Alias ,
254+ HeadComment : node .HeadComment ,
255+ LineComment : node .LineComment ,
256+ FootComment : node .FootComment ,
257+ Line : node .Line ,
258+ Column : node .Column ,
259+ }
260+
261+ if node .Content != nil {
262+ clone .Content = make ([]* yaml.Node , len (node .Content ))
263+ for i , child := range node .Content {
264+ clone .Content [i ] = cloneNode (child )
265+ }
266+ }
267+
268+ return clone
78269}
79270
80271func TestApplyToOld (t * testing.T ) {
0 commit comments