package world_test import ( "fmt" "io/ioutil" "sort" "testing" "github.com/inkyblackness/hacked/ss1/resource" "github.com/inkyblackness/hacked/ss1/world" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) type ModSuite struct { suite.Suite mod *world.Mod selector *resource.Selector lastModifiedIDs []resource.ID lastFailedIDs []resource.ID } func TestModSuite(t *testing.T) { suite.Run(t, new(ModSuite)) } func (suite *ModSuite) SetupTest() { suite.mod = world.NewMod(suite.onResourcesModified, func() {}) suite.selector = nil suite.lastModifiedIDs = nil suite.lastFailedIDs = nil } func (suite *ModSuite) onResourcesModified(modifiedIDs []resource.ID, failedIDs []resource.ID) { suite.lastModifiedIDs = modifiedIDs suite.lastFailedIDs = failedIDs } func (suite *ModSuite) TestResourcesCanBeRetrievedFromTheWorld() { suite.givenWorldHas( suite.someLocalizedResources(resource.LangAny, suite.storing(0x0800, [][]byte{{0xAA}}))) suite.whenResourcesAreQueriedFor(resource.LangAny) suite.thenResourcesCanBeSelected(0x0800) } func (suite *ModSuite) TestResourcesCanBeModified() { suite.givenWorldHas( suite.someLocalizedResources(resource.LangAny, suite.storing(0x0800, [][]byte{{0xAA}}))) suite.whenModifyingBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x0800, 0, []byte{0xBB}) }) suite.thenResourceBlockShouldBe(resource.LangAny, 0x0800, 0, []byte{0xBB}) } func (suite *ModSuite) TestResourcesCanBeExtended() { suite.givenWorldHas( suite.someLocalizedResources(resource.LangAny, suite.storing(0x0800, [][]byte{{0xAA}}))) suite.whenModifyingBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x0800, 2, []byte{0xBB}) }) suite.thenResourceBlockShouldBe(resource.LangAny, 0x0800, 2, []byte{0xBB}) } func (suite *ModSuite) TestResourcesCanBeRemoved() { suite.givenWorldHas( suite.someLocalizedResources(resource.LangAny, suite.storing(0x0800, [][]byte{{0xAA}}))) suite.givenModifiedBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x0800, 0, []byte{0xBB}) }) suite.whenModifyingBy(func(modder world.Modder) { modder.DelResource(resource.LangAny, 0x0800) }) suite.thenResourceBlockShouldBe(resource.LangAny, 0x0800, 0, []byte{0xAA}) } func (suite *ModSuite) TestAdditionsAreNotified() { suite.givenWorldHas( suite.someLocalizedResources(resource.LangAny, suite.storing(0x0800, [][]byte{{0xAA}}))) suite.whenModifyingBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x0800, 2, []byte{0xBB}) }) suite.thenModifiedResourcesShouldBe(0x0800) } func (suite *ModSuite) TestModificationsToIdenticalDataAreNotNotified() { suite.givenWorldHas( suite.someLocalizedResources(resource.LangAny, suite.storing(0x004C, [][]byte{{0xAA}}))) suite.whenModifyingBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x004C, 0, []byte{0xAA}) }) suite.thenModifiedResourcesShouldBe() } func (suite *ModSuite) TestDeletionIsNotified() { suite.givenWorldHas( suite.someLocalizedResources(resource.LangAny, suite.storing(0x0800, [][]byte{{0xAA}}))) suite.givenModifiedBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x0800, 0, []byte{0xBB}) }) suite.whenModifyingBy(func(modder world.Modder) { modder.DelResource(resource.LangAny, 0x0800) }) suite.thenModifiedResourcesShouldBe(0x0800) } func (suite *ModSuite) TestModifiedResourceCanBeRetrieved() { suite.whenModifyingBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x0800, 0, []byte{0xBB}) modder.SetResourceBlock(resource.LangAny, 0x0800, 1, []byte{0xCC}) }) res := suite.mod.ModifiedResource(resource.LangAny, 0x0800) require.NotNil(suite.T(), res, "Resource expected") assert.Equal(suite.T(), 2, res.BlockCount(), "Two blocks expected") } func (suite *ModSuite) TestModifiedBlocksCanBeRetrievedSingle() { suite.whenModifyingBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x0800, 0, []byte{0xBB}) modder.SetResourceBlock(resource.LangAny, 0x0800, 1, []byte{0xCC}) }) assert.Equal(suite.T(), []byte{0xBB}, suite.mod.ModifiedBlock(resource.LangAny, 0x0800, 0)) assert.Equal(suite.T(), []byte{0xCC}, suite.mod.ModifiedBlock(resource.LangAny, 0x0800, 1)) } func (suite *ModSuite) TestModifiedBlocksCanBeRetrievedBulk() { suite.whenModifyingBy(func(modder world.Modder) { modder.SetResourceBlock(resource.LangAny, 0x0800, 0, []byte{0xBB}) modder.SetResourceBlock(resource.LangAny, 0x0800, 1, []byte{0xCC}) }) assert.Equal(suite.T(), [][]byte{{0xBB}, {0xCC}}, suite.mod.ModifiedBlocks(resource.LangAny, 0x0800)) } func (suite *ModSuite) givenWorldHas(res ...resource.LocalizedResources) { suite.whenWorldIsExtendedWith(res...) suite.lastModifiedIDs = nil suite.lastFailedIDs = nil } func (suite *ModSuite) whenWorldIsExtendedWith(res ...resource.LocalizedResources) { manifest := suite.mod.World() at := manifest.EntryCount() id := fmt.Sprintf("entry-%d", at) err := suite.mod.World().InsertEntry(at, suite.anEntryWithResources(id, res...)) require.Nil(suite.T(), err, fmt.Sprintf("No error expected inserting entry at %d", at)) } func (suite *ModSuite) whenResourcesAreQueriedFor(lang resource.Language) { selector := suite.mod.LocalizedResources(lang) suite.selector = &selector } func (suite *ModSuite) givenModifiedBy(modifier func(world.Modder)) { suite.mod.Modify(modifier) suite.lastModifiedIDs = nil suite.lastFailedIDs = nil } func (suite *ModSuite) whenModifyingBy(modifier func(world.Modder)) { suite.mod.Modify(modifier) } func (suite *ModSuite) thenResourceBlockShouldBe(lang resource.Language, id int, blockIndex int, expected []byte) { view, viewErr := suite.mod.LocalizedResources(lang).Select(resource.ID(id)) require.Nil(suite.T(), viewErr, "No error expected selecting resource") reader, blockErr := view.Block(blockIndex) require.Nil(suite.T(), blockErr, "No error expected retrieving block") data, dataErr := ioutil.ReadAll(reader) require.Nil(suite.T(), dataErr, "No error expected reading data") assert.Equal(suite.T(), expected, data, "Data mismatch in block") } func (suite *ModSuite) thenResourcesCanBeSelected(id int) { view, err := suite.selector.Select(resource.ID(id)) assert.Nil(suite.T(), err, "No error expected") assert.NotNil(suite.T(), view, "View expected") } func (suite *ModSuite) thenModifiedResourcesShouldBe(expected ...int) { identified := make([]resource.ID, len(expected)) for index, id := range expected { identified[index] = resource.ID(id) } suite.sortIDs(identified) suite.sortIDs(suite.lastModifiedIDs) assert.Equal(suite.T(), identified, suite.lastModifiedIDs, "Modified IDs don't match") } func (suite *ModSuite) sortIDs(ids []resource.ID) { sort.Slice(ids, func(a, b int) bool { return ids[a] < ids[b] }) } func (suite *ModSuite) someLocalizedResources(lang resource.Language, modifiers ...func(*resource.Store)) resource.LocalizedResources { var store resource.Store for _, modifier := range modifiers { modifier(&store) } return resource.LocalizedResources{ ID: "unnamed", Language: lang, Viewer: store, } } func (suite *ModSuite) anEntryWithResources(id string, res ...resource.LocalizedResources) *world.ManifestEntry { return &world.ManifestEntry{ ID: id, Resources: res, } } func (suite *ModSuite) storing(id int, data [][]byte) func(*resource.Store) { return func(store *resource.Store) { _ = store.Put(resource.ID(id), resource.Resource{ Blocks: resource.BlocksFrom(data), }) } }