diff --git a/Famix-Simple-Diff/FamixDiff.class.st b/Famix-Simple-Diff/FamixDiff.class.st new file mode 100644 index 0000000..a2ca0b4 --- /dev/null +++ b/Famix-Simple-Diff/FamixDiff.class.st @@ -0,0 +1,93 @@ +Class { + #name : 'FamixDiff', + #superclass : 'Object', + #instVars : [ + 'entity', + 'changeType', + 'expectedValue', + 'actualValue' + ], + #category : 'Famix-Simple-Diff', + #package : 'Famix-Simple-Diff' +} + +{ #category : 'instance creation' } +FamixDiff class >> entity: anEntity changeType: aSymbol expectedValue: val1 actualValue: val2 [ + + | difference | + difference := FamixDiff new. + difference entity: anEntity. + difference changeType: aSymbol. + difference expectedValue: val1. + difference actualValue: val2. + ^ difference +] + +{ #category : 'accessing' } +FamixDiff >> actualValue [ + + ^ actualValue +] + +{ #category : 'accessing' } +FamixDiff >> actualValue: anObject [ + + actualValue := anObject +] + +{ #category : 'accessing' } +FamixDiff >> changeType [ + + ^ changeType +] + +{ #category : 'accessing' } +FamixDiff >> changeType: anObject [ + + changeType := anObject +] + +{ #category : 'accessing' } +FamixDiff >> entity [ + + ^ entity +] + +{ #category : 'accessing' } +FamixDiff >> entity: anObject [ + + entity := anObject +] + +{ #category : 'accessing' } +FamixDiff >> expectedValue [ + + ^ expectedValue +] + +{ #category : 'accessing' } +FamixDiff >> expectedValue: anObject [ + + expectedValue := anObject +] + +{ #category : 'printing' } +FamixDiff >> printOn: aStream [ + "Redifine the display of the FamixDiff class" + + "Will build a Stream like: FamixDiff(Addition -> Expected: ' ' | Actual: 'private int something' | On Class1)" + + super printOn: aStream. + aStream nextPutAll: '('. + aStream print: changeType. + + aStream + nextPutAll: ' -> Expected: '; + print: expectedValue; + nextPutAll: ' | Actual: '; + print: actualValue. + + aStream nextPutAll: ' | On: '. + aStream print: entity. + aStream nextPutAll: ')'. +] diff --git a/Famix-Simple-Diff/FamixDiffTest.class.st b/Famix-Simple-Diff/FamixDiffTest.class.st new file mode 100644 index 0000000..c4102c4 --- /dev/null +++ b/Famix-Simple-Diff/FamixDiffTest.class.st @@ -0,0 +1,27 @@ +Class { + #name : 'FamixDiffTest', + #superclass : 'TestCase', + #category : 'Famix-Simple-Diff', + #package : 'Famix-Simple-Diff' +} + +{ #category : 'tests' } +FamixDiffTest >> testInstanceCreation [ + | difference expectedValue actualValue | + + "testing a code modification" + expectedValue := 'private int money;'. + actualValue := 'private int age;'. + + difference := FamixDiff + entity: 'testEntity' + changeType: #entityModification + expectedValue: expectedValue + actualValue: actualValue. + + "checking instance creation" + self assert: difference entity equals: 'testEntity'. + self assert: difference changeType equals: #entityModification. + self assert: difference expectedValue equals: expectedValue. + self assert: difference actualValue equals: actualValue. +] diff --git a/Famix-Simple-Diff/FamixSimpleDiff.class.st b/Famix-Simple-Diff/FamixSimpleDiff.class.st index cab3f48..00203cb 100644 --- a/Famix-Simple-Diff/FamixSimpleDiff.class.st +++ b/Famix-Simple-Diff/FamixSimpleDiff.class.st @@ -4,24 +4,58 @@ Class { #instVars : [ 'roots', 'childrenCache', - 'debug' + 'debug', + 'differences' ], #category : 'Famix-Simple-Diff', #package : 'Famix-Simple-Diff' } +{ #category : 'accessing' } +FamixSimpleDiff >> addDifference: aFamixDiff [ + differences add: aFamixDiff +] + { #category : 'comparing' } FamixSimpleDiff >> compareEntity: aFamixEntity to: otherFamixEntity [ - | properties relations | - aFamixEntity class = otherFamixEntity class ifFalse: [ ^ self fail ]. + | properties relations diff isEquals | + isEquals := true. + + "we compare the type of the entities. If we have not the same type, we add the difference in the list" + aFamixEntity class = otherFamixEntity class ifFalse: [ + diff := ( + FamixDiff + entity: otherFamixEntity + changeType: #entityChanged + expectedValue: aFamixEntity class name + actualValue: otherFamixEntity class name + ). + + self addDifference: diff. + ^ self fail + ]. "Comparing properties (slots with non-famix values)" properties := aFamixEntity class allSlots select: [ :slot | slot class = FMProperty ]. properties do: [ :p | (p read: aFamixEntity) = (p read: otherFamixEntity) ifFalse: [ - ^ self fail ] ]. + | prop1 prop2| + prop1 := p read: aFamixEntity. + prop2 := p read: otherFamixEntity. + + diff := ( + FamixDiff + entity: otherFamixEntity + changeType: #propertyChanged + expectedValue: prop1 + actualValue: prop2 + ). + self addDifference: diff. + isEquals := false + ] + ]. "Comparing relations (slots with famix values)" "We exclude source anchors because they are not relevant to model comparison" @@ -29,8 +63,11 @@ FamixSimpleDiff >> compareEntity: aFamixEntity to: otherFamixEntity [ slot isFMRelationSlot and: [ slot name ~= 'sourceAnchor' ] ]. relations do: [ :r | (self compareRelation: r from: aFamixEntity to: otherFamixEntity) - ifFalse: [ ^ self fail ] ]. + ifFalse: [ isEquals := false ] ]. + "we stop the execution here if models are differents" + isEquals ifFalse: [ ^ self fail ]. + "Every slot value is equal, the entities are identical" ^ true ] @@ -52,7 +89,9 @@ FamixSimpleDiff >> compareModel: aFamixJavaModel to: aFamixJavaModel2 [ { #category : 'comparing' } FamixSimpleDiff >> compareRelation: aFMRelationSlot from: aFamixEntity to: otherFamixEntity [ - | val1 val2 sortBlock | + | val1 val2 sortBlock isEquals diff| + isEquals := true. + aFMRelationSlot isToOne ifTrue: [ val1 := aFMRelationSlot read: aFamixEntity. @@ -60,8 +99,7 @@ FamixSimpleDiff >> compareRelation: aFMRelationSlot from: aFamixEntity to: other ifFalse: [ | rejectBlock | val1 := (aFMRelationSlot read: aFamixEntity) asOrderedCollection. - val2 := (aFMRelationSlot read: otherFamixEntity) - asOrderedCollection. + val2 := (aFMRelationSlot read: otherFamixEntity)asOrderedCollection. sortBlock := val1 ifEmpty: [ self sortBlock ] @@ -80,19 +118,80 @@ FamixSimpleDiff >> compareRelation: aFMRelationSlot from: aFamixEntity to: other val1 == val2 ifTrue: [ ^ true ]. aFMRelationSlot isToOne - ifTrue: [ ^ (self pathesForMaybeMultiRelation: val1) = (self pathesForMaybeMultiRelation: val2) ] - ifFalse: [ "This is a toMany Relation! and the value should be a collection" - val1 size = val2 size ifFalse: [ ^ self fail ]. - val1 := val1 + ifTrue: [ + | areSame | + "if we dont have the same relations, so it changed, we have to record it" + areSame := (self pathesForMaybeMultiRelation: val1) = (self pathesForMaybeMultiRelation: val2). + areSame ifFalse: [ + diff := ( + FamixDiff + entity: otherFamixEntity + changeType: #relationChanged + expectedValue: val1 + actualValue: val2 + ). + self addDifference: diff. + isEquals := false. + ] + ] + + "This is a toMany Relation! and the value should be a collection" + ifFalse: [ + + "if we have different sizes of val, here we have an addition or a deletion" + "val1 size = val2 size ifFalse: [ ^ self fail ]." + + | expectedBag actualBag| + expectedBag := val1 flatCollect: [ :e | self pathesForMaybeMultiRelation: e ] as: Bag. - val2 := val2 + actualBag := val2 flatCollect: [ :e | self pathesForMaybeMultiRelation: e ] as: Bag. - val1 = val2 - ifFalse: [ ^ self fail ] - ifTrue: [ ^ true ] ] + "compute additions and deletion between bags" + expectedBag = actualBag ifFalse: [ + isEquals := false. + + "deletion case, here in the model 1, but not in model 2" + val1 do:[ :expectedEntity | + | path allInBag| + "check the path of the expected entity, if all the paths are not in the expectedBag, so we have a deletion" + path := self pathesForMaybeMultiRelation: expectedEntity. + allInBag := (path allSatisfy: [ :p | actualBag includes: p]). + allInBag ifFalse: [ + diff := ( + FamixDiff + entity: otherFamixEntity + changeType: #entityRemoved + expectedValue: expectedEntity + actualValue: nil + ). + self addDifference: diff. + ]. + ]. + + "addition case, not here in the model 1, but here in the model 2" + val2 do:[ :actualEntity | + | path allInBag| + "check the path of the actual entity, if all the paths are not in the actual, so we have a addition" + path := self pathesForMaybeMultiRelation: actualEntity . + allInBag := (path allSatisfy: [ :p | expectedBag includes: p]). + allInBag ifFalse: [ + diff := ( + FamixDiff + entity: otherFamixEntity + changeType: #entityAdded + expectedValue: nil + actualValue: actualEntity + ). + self addDifference: diff. + ]. + ]. + ]. + ]. + + ^ isEquals. ] { #category : 'path-computing' } @@ -127,6 +226,11 @@ FamixSimpleDiff >> debugOn [ debug := true ] +{ #category : 'accessing' } +FamixSimpleDiff >> differences [ + ^ differences +] + { #category : 'path-computing' } FamixSimpleDiff >> entityAtPath: indexPath fromEntity: entity [ @@ -178,7 +282,8 @@ FamixSimpleDiff >> initialize [ super initialize. debug := false. - childrenCache := LRUCache new maximumWeight: 100 + childrenCache := LRUCache new maximumWeight: 100. + differences := OrderedCollection new. ] { #category : 'path-computing' } diff --git a/Famix-Simple-Diff/FamixSimpleDiffTest.class.st b/Famix-Simple-Diff/FamixSimpleDiffTest.class.st index 1d3b5c0..47f5d2e 100644 --- a/Famix-Simple-Diff/FamixSimpleDiffTest.class.st +++ b/Famix-Simple-Diff/FamixSimpleDiffTest.class.st @@ -50,6 +50,157 @@ FamixSimpleDiffTest >> testClassesWithDifferentSuperclassesAreNotEqual [ self assert: (sd compareEntity: entity1 to: entity2) equals: false ] +{ #category : 'tests' } +FamixSimpleDiffTest >> testCompareEntityToReturnsFalseAndRecordsDiffWhenDifferentClasses [ + | sd class1 class2 diff | + sd := FamixSimpleDiff new. + + "different classes" + class1 := FamixJavaClass new name: 'MyClass'. + class2 := FamixJavaClass new name: 'MyOtherClass'. + + "checking the list of diff" + self deny: (sd compareEntity: class1 to: class2). + self deny: sd differences isEmpty. + self assert: sd differences size equals: 1. + + diff := sd differences first. + + self assert: diff changeType equals: #propertyChanged. + self assert: diff expectedValue equals: 'MyClass'. + self assert: diff actualValue equals: 'MyOtherClass'. + +] + +{ #category : 'tests' } +FamixSimpleDiffTest >> testCompareEntityToReturnsFalseAndRecordsDiffWhenDifferentType [ + | sd class method diff | + sd := FamixSimpleDiff new. + + "different classes" + class := FamixJavaClass new name: 'MyClass'. + method := FamixJavaMethod new name: 'MyMethod'. + + "checking the list of diff" + self deny: (sd compareEntity: class to: method). + self deny: sd differences isEmpty. + self assert: sd differences size equals: 1. + + diff := sd differences first. + + self assert: diff changeType equals: #entityChanged. + self assert: diff expectedValue equals: 'FamixJavaClass'. + self assert: diff actualValue equals: 'FamixJavaMethod'. + +] + +{ #category : 'tests' } +FamixSimpleDiffTest >> testCompareEntityToReturnsFalseWhenSameClassesAndAMethodIsAdded [ + | sd class1 class2 method1 method2 method3 model1 model2 diff | + sd := FamixSimpleDiff new. + + "we need to create a model to contain our entities" + model1 := FamixJavaModel new. + model2 := FamixJavaModel new. + + "same classes" + class1 := FamixJavaClass new name: 'MyClass'. + class2 := FamixJavaClass new name: 'MyClass'. + + "same methods" + method1 := FamixJavaMethod new name: 'myMethod'. + method2 := FamixJavaMethod new name: 'myMethod'. + method3 := FamixJavaMethod new name: 'newMethod'. + + class1 methods: { method1 }. + class2 methods: { method3 . method2 }. + + model1 add: class1. + model2 add: class2. + + self deny: (sd compareEntity: class1 to: class2). + self assert: sd differences size equals: 1. + + diff := sd differences first. + + self assert: diff changeType equals: #entityAdded. + self assert: diff expectedValue equals: nil. + self assert: diff actualValue name equals: 'newMethod'. +] + +{ #category : 'tests' } +FamixSimpleDiffTest >> testCompareEntityToReturnsFalseWhenSameClassesAndAMethodIsDeleted [ + | sd class1 class2 method1 method2 method3 model1 model2 diff | + sd := FamixSimpleDiff new. + + "we need to create a model to contain our entities" + model1 := FamixJavaModel new. + model2 := FamixJavaModel new. + + "same classes" + class1 := FamixJavaClass new name: 'MyClass'. + class2 := FamixJavaClass new name: 'MyClass'. + + "methods" + method1 := FamixJavaMethod new name: 'myMethod'. + method2 := FamixJavaMethod new name: 'myMethod'. + method3 := FamixJavaMethod new name: 'deletedMethod'. + class1 methods: { method3 . method1 }. + class2 methods: { method2 }. + + model1 add: class1. + model2 add: class2. + + self deny: (sd compareEntity: class1 to: class2). + self assert: sd differences size equals: 1. + + diff := sd differences first. + + self assert: diff changeType equals: #entityRemoved. + self assert: diff expectedValue name equals: 'deletedMethod'. + self assert: diff actualValue equals: nil. +] + +{ #category : 'tests' } +FamixSimpleDiffTest >> testCompareEntityToReturnsTrueAndEmptyDiffListWhenSameClasses [ + | sd class1 class2 | + sd := FamixSimpleDiff new. + + "same classes" + class1 := FamixJavaClass new name: 'MyClass'. + class2 := FamixJavaClass new name: 'MyClass'. + + self assert: (sd compareEntity: class1 to: class2). + self assert: sd differences isEmpty. +] + +{ #category : 'tests' } +FamixSimpleDiffTest >> testCompareEntityToReturnsTrueWhenSameClassesAndSameMethods [ + | sd class1 class2 method1 method2 model1 model2 | + sd := FamixSimpleDiff new. + + "we need to create a model to contain our entities" + model1 := FamixJavaModel new. + model2 := FamixJavaModel new. + + "same classes" + class1 := FamixJavaClass new name: 'MyClass'. + class2 := FamixJavaClass new name: 'MyClass'. + + "same methods" + method1 := FamixJavaMethod new name: 'myMethod'. + method2 := FamixJavaMethod new name: 'myMethod'. + + class1 methods: { method1 }. + class2 methods: { method2 }. + + model1 add: class1. + model2 add: class2. + + self assert: (sd compareEntity: class1 to: class2). + self assert: sd differences isEmpty. +] + { #category : 'tests' } FamixSimpleDiffTest >> testCorrespondingClassesAreEqual [