github.com/sdqri/sequined@v0.0.0-20240421190656-fc6bf956f4d8/internal/graphgenerator/graph_generator_test.go (about) 1 package graphgenerator_test 2 3 import ( 4 "fmt" 5 "sort" 6 "testing" 7 "time" 8 9 "github.com/pkg/errors" 10 "github.com/sdqri/sequined/internal/graphgenerator" 11 hr "github.com/sdqri/sequined/internal/hyperrenderer" 12 "github.com/stretchr/testify/assert" 13 ) 14 15 type RootGenerator func() *hr.Webpage 16 17 func CreateMockSelectByProbability() (graphgenerator.SelectorFunc, chan []float64) { 18 probabilitiesChan := make(chan []float64, 1) 19 return func(probabilities []float64) (int, error) { 20 probabilitiesChan <- probabilities 21 return graphgenerator.SelectByProbability(probabilities) 22 }, probabilitiesChan 23 } 24 25 func TestCreateHubPage(t *testing.T) { 26 testCases := []struct { 27 name string 28 rootGenerator RootGenerator 29 preferentialAttachment float64 30 expectedProbabilites []float64 31 }{ 32 { 33 name: "PreferentialAttachment=1", 34 rootGenerator: func() *hr.Webpage { 35 root := hr.NewWebpage(hr.WebpageTypeHub) 36 root.AddChild(hr.WebpageTypeHub) 37 return root 38 }, 39 preferentialAttachment: 1, 40 expectedProbabilites: []float64{1, 0}, 41 }, 42 { 43 name: "PreferentialAttachment=0", 44 rootGenerator: func() *hr.Webpage { 45 root := hr.NewWebpage(hr.WebpageTypeHub) 46 root.AddChild(hr.WebpageTypeHub) 47 return root 48 }, 49 preferentialAttachment: 0, 50 expectedProbabilites: []float64{0.5, 0.5}, 51 }, 52 { 53 name: "PreferentialAttachment=0.5", 54 rootGenerator: func() *hr.Webpage { 55 root := hr.NewWebpage(hr.WebpageTypeHub) 56 root.AddChild(hr.WebpageTypeHub) 57 return root 58 }, 59 preferentialAttachment: 0.5, 60 expectedProbabilites: []float64{0.75, 0.25}, 61 }, 62 } 63 64 for _, tc := range testCases { 65 t.Run(tc.name, func(t *testing.T) { 66 gg := graphgenerator.New(tc.rootGenerator(), tc.preferentialAttachment) 67 f, probabilitesChan := CreateMockSelectByProbability() 68 gg.SelectorFunc = f 69 webpage, err := gg.CreateHubPage() 70 assert.NoError(t, err, "Error creating hub page") 71 actualProbabilities := <-probabilitesChan 72 sort.Sort(sort.Float64Slice(actualProbabilities)) 73 sort.Sort(sort.Float64Slice(tc.expectedProbabilites)) 74 assert.Equal(t, tc.expectedProbabilites, actualProbabilities) 75 assert.NotNil(t, webpage) 76 close(probabilitesChan) 77 }) 78 } 79 } 80 81 func TestCreateAuthorityPage(t *testing.T) { 82 testCases := []struct { 83 name string 84 rootGenerator RootGenerator 85 preferentialAttachment float64 86 expectedProbabilites []float64 87 }{ 88 { 89 name: "PreferentialAttachment=1", 90 rootGenerator: func() *hr.Webpage { 91 root := hr.NewWebpage(hr.WebpageTypeHub) 92 childHub := root.AddChild(hr.WebpageTypeHub) 93 childHub.AddChild(hr.WebpageTypeAuthority) 94 childHub.AddChild(hr.WebpageTypeAuthority) 95 childHub.AddChild(hr.WebpageTypeAuthority) 96 return root 97 }, 98 preferentialAttachment: 1, 99 expectedProbabilites: []float64{0.75, 0.25}, 100 }, 101 { 102 name: "PreferentialAttachment=0", 103 rootGenerator: func() *hr.Webpage { 104 root := hr.NewWebpage(hr.WebpageTypeHub) 105 childHub := root.AddChild(hr.WebpageTypeHub) 106 childHub.AddChild(hr.WebpageTypeAuthority) 107 childHub.AddChild(hr.WebpageTypeAuthority) 108 childHub.AddChild(hr.WebpageTypeAuthority) 109 return root 110 }, 111 preferentialAttachment: 0, 112 expectedProbabilites: []float64{0.5, 0.5}, 113 }, 114 { 115 name: "PreferentialAttachment=0.5", 116 rootGenerator: func() *hr.Webpage { 117 root := hr.NewWebpage(hr.WebpageTypeHub) 118 childHub := root.AddChild(hr.WebpageTypeHub) 119 childHub.AddChild(hr.WebpageTypeAuthority) 120 childHub.AddChild(hr.WebpageTypeAuthority) 121 childHub.AddChild(hr.WebpageTypeAuthority) 122 return root 123 }, 124 preferentialAttachment: 0.5, 125 expectedProbabilites: []float64{0.375, 0.625}, 126 }, 127 } 128 129 for _, tc := range testCases { 130 t.Run(tc.name, func(t *testing.T) { 131 gg := graphgenerator.New(tc.rootGenerator(), tc.preferentialAttachment) 132 f, probabilitesChan := CreateMockSelectByProbability() 133 gg.SelectorFunc = f 134 webpage, err := gg.CreateAuthorityPage() 135 assert.NoError(t, err, "Error creating hub page") 136 actualProbabilities := <-probabilitesChan 137 sort.Sort(sort.Float64Slice(actualProbabilities)) 138 sort.Sort(sort.Float64Slice(tc.expectedProbabilites)) 139 assert.Equal(t, tc.expectedProbabilites, actualProbabilities) 140 assert.NotNil(t, webpage) 141 close(probabilitesChan) 142 }) 143 } 144 } 145 146 func TestGenerate(t *testing.T) { 147 testCases := []struct { 148 name string 149 rootGenerator RootGenerator 150 preferentialAttachment float64 151 maxHubCount int 152 maxAuthCount int 153 expectedError error 154 }{ 155 { 156 name: "nomral generate", 157 rootGenerator: func() *hr.Webpage { 158 return hr.NewWebpage(hr.WebpageTypeHub) 159 }, 160 preferentialAttachment: 0.5, 161 maxHubCount: 10, 162 maxAuthCount: 10, 163 expectedError: nil, 164 }, 165 { 166 name: "exceeded max generate", 167 rootGenerator: func() *hr.Webpage { 168 root := hr.NewWebpage(hr.WebpageTypeHub) 169 root.AddChild(hr.WebpageTypeAuthority).AddChild(hr.WebpageTypeAuthority) 170 root.AddChild(hr.WebpageTypeHub).AddChild(hr.WebpageTypeHub) 171 return root 172 }, 173 preferentialAttachment: 0.5, 174 maxHubCount: 2, 175 maxAuthCount: 2, 176 expectedError: graphgenerator.ErrMaxHubOrAuthCountAlreadyExceeded, 177 }, 178 } 179 180 for _, tc := range testCases { 181 t.Run(tc.name, func(t *testing.T) { 182 root := tc.rootGenerator() 183 gg := graphgenerator.New(root, tc.preferentialAttachment) 184 err := gg.Generate(tc.maxHubCount, tc.maxAuthCount) 185 assert.Equal(t, err, tc.expectedError, "Unexpected error while calling gg.Generate") 186 if tc.expectedError == nil { 187 hubCount, authCount := 0, 0 188 hr.Traverse(root, func(currentRenderer hr.HyperRenderer) bool { 189 currentPage, ok := currentRenderer.(*hr.Webpage) 190 if !ok { 191 t.Errorf("Unable to traverse because non Webpage node") 192 return true 193 } 194 195 if currentPage.Type == hr.WebpageTypeAuthority { 196 authCount++ 197 } else if currentPage.Type == hr.WebpageTypeHub { 198 hubCount++ 199 } 200 return false 201 }) 202 assert.Equal(t, tc.maxHubCount, hubCount) 203 assert.Equal(t, tc.maxAuthCount, authCount) 204 } 205 }) 206 } 207 } 208 209 func TestStartGraphEvolution(t *testing.T) { 210 testCases := []struct { 211 name string 212 rootGenerator RootGenerator 213 preferentialAttachment float64 214 maxHubCount int 215 maxAuthCount int 216 expectedError error 217 expectedCountUpdateMessage int 218 waitFor time.Duration 219 }{ 220 { 221 name: "nomral generate", 222 rootGenerator: func() *hr.Webpage { 223 return hr.NewWebpage(hr.WebpageTypeHub) 224 }, 225 preferentialAttachment: 0.5, 226 maxHubCount: 10, 227 maxAuthCount: 5, 228 expectedError: nil, 229 expectedCountUpdateMessage: 9 + 5, 230 waitFor: (9 + 5) * 2 * (time.Hour / 1_000_000), 231 }, 232 { 233 name: "big generate", 234 rootGenerator: func() *hr.Webpage { 235 return hr.NewWebpage(hr.WebpageTypeHub) 236 }, 237 preferentialAttachment: 0.5, 238 maxHubCount: 100, 239 maxAuthCount: 5000, 240 expectedError: nil, 241 expectedCountUpdateMessage: 100 + 5000, 242 waitFor: (100 + 5000) * 2 * (time.Hour / 1_000_000), 243 }, 244 { 245 name: "exceeded max generate", 246 rootGenerator: func() *hr.Webpage { 247 root := hr.NewWebpage(hr.WebpageTypeHub) 248 root.AddChild(hr.WebpageTypeAuthority).AddChild(hr.WebpageTypeAuthority) 249 root.AddChild(hr.WebpageTypeHub).AddChild(hr.WebpageTypeHub) 250 return root 251 }, 252 preferentialAttachment: 0.5, 253 maxHubCount: 2, 254 maxAuthCount: 2, 255 expectedError: graphgenerator.ErrMaxHubOrAuthCountAlreadyExceeded, 256 }, 257 } 258 259 for _, tc := range testCases { 260 t.Run(tc.name, func(t *testing.T) { 261 root := tc.rootGenerator() 262 gg := graphgenerator.New(root, tc.preferentialAttachment) 263 gg.Debug = true 264 updateChan, errChan, err := gg.StartGraphEvolution( 265 tc.maxHubCount, tc.maxAuthCount, 266 1_000_000, 1_000_000, 267 ) 268 assert.Equal(t, err, tc.expectedError, "Unexpected error while calling gg.Generate") 269 if tc.expectedError == nil { 270 countUpdateMessage := 0 271 outerLoop: 272 for { 273 select { 274 case <-updateChan: 275 countUpdateMessage++ 276 if countUpdateMessage == tc.expectedCountUpdateMessage { 277 assert.Equal(t, countUpdateMessage, tc.expectedCountUpdateMessage) 278 break outerLoop 279 } 280 case err, ok := <-errChan: 281 if ok { 282 assert.FailNow(t, fmt.Sprintf("Received an unexpected error from errChan, err:%s", err.Error())) 283 break outerLoop 284 } 285 case <-time.After(tc.waitFor): 286 assert.Error(t, errors.Errorf("Expected number of updateMessage not reached in wait time")) 287 break outerLoop 288 } 289 } 290 hubCount, authCount := 0, 0 291 hr.Traverse(root, func(currentRenderer hr.HyperRenderer) bool { 292 currentPage, ok := currentRenderer.(*hr.Webpage) 293 if !ok { 294 t.Errorf("Unable to traverse because non Webpage node") 295 return true 296 } 297 298 if currentPage.Type == hr.WebpageTypeAuthority { 299 authCount++ 300 } else if currentPage.Type == hr.WebpageTypeHub { 301 hubCount++ 302 } 303 return false 304 }) 305 assert.Equal(t, tc.maxHubCount, hubCount) 306 assert.Equal(t, tc.maxAuthCount, authCount) 307 308 } 309 }) 310 } 311 }