Skip to content

Commit 180cc0b

Browse files
committed
FELIX-6773 ResolverImpl downloading was bundles it doesn't need to due to LazyLocalResourceImpl equals not forwarding its call to the resource it's wrapping.
1 parent 7dd6b76 commit 180cc0b

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed

bundlerepository/src/main/java/org/apache/felix/bundlerepository/impl/LazyLocalResourceImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,12 @@ public Capability[] getCapabilities() {
111111
public Requirement[] getRequirements() {
112112
return getResource().getRequirements();
113113
}
114+
115+
public boolean equals(Object o) {
116+
return getResource().equals(o);
117+
}
118+
119+
public int hashCode() {
120+
return getResource().hashCode();
121+
}
114122
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.felix.bundlerepository.impl;
20+
21+
import junit.framework.TestCase;
22+
import org.apache.felix.utils.log.Logger;
23+
import org.easymock.EasyMock;
24+
import org.osgi.framework.*;
25+
26+
import java.util.Dictionary;
27+
import java.util.Hashtable;
28+
29+
public class LazyLocalResourceImplTest extends TestCase
30+
{
31+
public void testEquals() {
32+
// Create mock Bundles using the helper
33+
Bundle bundle1a = createMockBundle("test.bundle", "1.0.0", 1L);
34+
Bundle bundle1b = createMockBundle("test.bundle", "1.0.0", 2L); // Different ID, same identity
35+
Bundle bundle1c = createMockBundle("test.bundle", "1.0.0", 5L); // For transitivity test
36+
Bundle bundle2 = createMockBundle("test.bundle", "1.1.0", 3L); // Different version
37+
Bundle bundle3 = createMockBundle("another.bundle", "1.0.0", 4L); // Different BSN
38+
39+
// Replay all mocks created by the helper
40+
EasyMock.replay(bundle1a, bundle1b, bundle1c, bundle2, bundle3);
41+
42+
// Create LazyLocalResourceImpl instances
43+
LazyLocalResourceImpl res1a = new LazyLocalResourceImpl(bundle1a, new Logger(bundle1a.getBundleContext()));
44+
// Reference to the same object
45+
LazyLocalResourceImpl res1b = new LazyLocalResourceImpl(bundle1b, new Logger(bundle1b.getBundleContext()));
46+
LazyLocalResourceImpl res1c = new LazyLocalResourceImpl(bundle1c, new Logger(bundle1c.getBundleContext()));
47+
LazyLocalResourceImpl res2 = new LazyLocalResourceImpl(bundle2, new Logger(bundle2.getBundleContext()));
48+
LazyLocalResourceImpl res3 = new LazyLocalResourceImpl(bundle3, new Logger(bundle3.getBundleContext()));
49+
50+
// 1. Reflexive
51+
assertEquals("A resource must be equal to itself.", res1a, res1a);
52+
53+
// 2. Symmetric
54+
assertEquals("Resources with same BSN/Version should be equal (Symmetry Part 1).", res1a, res1b);
55+
assertEquals("Resources with same BSN/Version should be equal (Symmetry Part 2).", res1b, res1a);
56+
57+
// 3. Transitive
58+
assertEquals("res1b should be equal to res1c (Transitive premise 1).", res1b, res1c);
59+
assertEquals("res1a should be equal to res1c (Transitive conclusion).", res1a, res1c);
60+
61+
// 4. Consistency (Implicitly tested by repeated calls)
62+
assertEquals("Consistency check", res1a, res1b);
63+
64+
// 5. Null comparison
65+
assertFalse("Resource should not be equal to null.", res1a.equals(null));
66+
67+
// 6. Different type comparison
68+
assertFalse("Resource should not be equal to an object of a different type.", res1a.equals(new Object()));
69+
70+
// 7. Inequality cases
71+
assertFalse("Resources with different versions should not be equal.", res1a.equals(res2));
72+
assertFalse("Resources with different symbolic names should not be equal.", res1a.equals(res3));
73+
74+
// 8. Test with lazy initialization interaction
75+
Bundle bundleLazyA = createMockBundle("lazy.test", "1.0", 6L);
76+
Bundle bundleLazyB = createMockBundle("lazy.test", "1.0", 7L);
77+
EasyMock.replay(bundleLazyA, bundleLazyB); // Replay the lazy mocks
78+
79+
LazyLocalResourceImpl resLazyA = new LazyLocalResourceImpl(bundleLazyA, new Logger(bundleLazyA.getBundleContext()));
80+
LazyLocalResourceImpl resLazyB = new LazyLocalResourceImpl(bundleLazyB, new Logger(bundleLazyB.getBundleContext()));
81+
82+
// Check equality before internal Resource is initialized
83+
assertEquals("Equality should hold even before explicit initialization.", resLazyA, resLazyB);
84+
85+
// Force initialization of resLazyA's internal resource
86+
assertNotNull("Getting symbolic name should work", resLazyA.getSymbolicName());
87+
assertEquals("Equality should hold after one resource is initialized.", resLazyA, resLazyB);
88+
assertEquals("Symmetric equality should hold after one resource is initialized.", resLazyB, resLazyA);
89+
90+
// Force initialization of resLazyB's internal resource
91+
assertNotNull("Getting version should work", resLazyB.getVersion());
92+
assertEquals("Equality should hold after both resources are initialized.", resLazyA, resLazyB);
93+
94+
// Verify mocks - confirms expected methods were called on bundles
95+
EasyMock.verify(bundle1a, bundle1b, bundle1c, bundle2, bundle3, bundleLazyA, bundleLazyB);
96+
}
97+
98+
public void testHashCode() {
99+
// Create mock Bundles
100+
Bundle bundle1a = createMockBundle("hash.bundle", "1.0.0", 10L);
101+
Bundle bundle1b = createMockBundle("hash.bundle", "1.0.0", 11L); // Should be equal to 1a
102+
Bundle bundle2 = createMockBundle("hash.bundle", "2.0.0", 12L); // Different version
103+
Bundle bundle3 = createMockBundle("another.hash.bundle", "1.0.0", 13L); // Different BSN
104+
105+
// Replay mocks
106+
EasyMock.replay(bundle1a, bundle1b, bundle2, bundle3);
107+
108+
LazyLocalResourceImpl res1a = new LazyLocalResourceImpl(bundle1a, new Logger(bundle1a.getBundleContext()));
109+
LazyLocalResourceImpl res1b = new LazyLocalResourceImpl(bundle1b, new Logger(bundle1b.getBundleContext()));
110+
LazyLocalResourceImpl res2 = new LazyLocalResourceImpl(bundle2, new Logger(bundle2.getBundleContext()));
111+
LazyLocalResourceImpl res3 = new LazyLocalResourceImpl(bundle3, new Logger(bundle3.getBundleContext()));
112+
113+
// 1. Consistency
114+
int hash1a_call1 = res1a.hashCode();
115+
assertNotNull("Getting properties should work", res1a.getProperties()); // Access a property to potentially trigger initialization
116+
int hash1a_call2 = res1a.hashCode();
117+
assertEquals("HashCode must be consistent across multiple invocations.", hash1a_call1, hash1a_call2);
118+
119+
// 2. Equality implies equal hash codes
120+
assertEquals("Precondition failed: res1a should be equal to res1b.", res1a, res1b);
121+
assertEquals("Equal objects must have equal hash codes.", res1a.hashCode(), res1b.hashCode());
122+
123+
// Check inequality cases (hash codes *might* collide)
124+
assertFalse("Precondition failed: res1a should not be equal to res2.", res1a.equals(res2));
125+
assertFalse("Precondition failed: res1a should not be equal to res3.", res1a.equals(res3));
126+
127+
// 3. Test with lazy initialization interaction
128+
Bundle bundleLazyHash = createMockBundle("lazy.hash", "1.0", 14L);
129+
EasyMock.replay(bundleLazyHash); // Replay the lazy mock
130+
131+
LazyLocalResourceImpl resLazyHash = new LazyLocalResourceImpl(bundleLazyHash, new Logger(bundleLazyHash.getBundleContext()));
132+
133+
int hashBeforeInit = resLazyHash.hashCode();
134+
// Force initialization by accessing a property that delegates
135+
assertNotNull("Getting capabilities should work", resLazyHash.getCapabilities());
136+
int hashAfterInit = resLazyHash.hashCode();
137+
138+
assertEquals("HashCode should be consistent before and after lazy initialization.", hashBeforeInit, hashAfterInit);
139+
140+
// Verify mocks
141+
EasyMock.verify(bundle1a, bundle1b, bundle2, bundle3, bundleLazyHash);
142+
}
143+
144+
private Bundle createMockBundle(String symbolicName, String versionString, long bundleId) {
145+
Bundle mockBundle = EasyMock.createMock(Bundle.class);
146+
BundleContext mockContext = EasyMock.createNiceMock(BundleContext.class);
147+
Version version = (versionString != null) ? Version.parseVersion(versionString) : Version.emptyVersion;
148+
Dictionary<String, String> headers = new Hashtable<>();
149+
150+
if (symbolicName != null) {
151+
headers.put(Constants.BUNDLE_SYMBOLICNAME, symbolicName);
152+
// Add BSN directive if needed, assuming default visibility
153+
EasyMock.expect(mockBundle.getSymbolicName()).andReturn(symbolicName).anyTimes();
154+
} else {
155+
EasyMock.expect(mockBundle.getSymbolicName()).andReturn(null).anyTimes();
156+
}
157+
headers.put(Constants.BUNDLE_VERSION, version.toString());
158+
headers.put(Constants.BUNDLE_MANIFESTVERSION, "2"); // Often required by resource parsers
159+
160+
EasyMock.expect(mockBundle.getHeaders()).andReturn(headers).anyTimes();
161+
EasyMock.expect(mockBundle.getHeaders(EasyMock.anyString())).andReturn(headers).anyTimes(); // Handle locale variant
162+
EasyMock.expect(mockBundle.getVersion()).andReturn(version).anyTimes();
163+
EasyMock.expect(mockBundle.getBundleId()).andReturn(bundleId).anyTimes();
164+
EasyMock.expect(mockBundle.getLocation()).andReturn("mock:/" + bundleId).anyTimes(); // For Resource ID
165+
EasyMock.expect(mockBundle.getBundleContext()).andReturn(mockContext).anyTimes();
166+
EasyMock.expect(mockBundle.getRegisteredServices()).andReturn(new ServiceReference[0]).anyTimes();
167+
168+
return mockBundle;
169+
}
170+
}

0 commit comments

Comments
 (0)