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  }