88from django_github_app .routing import GitHubRouter
99from django_github_app .views import BaseWebhookView
1010
11- pytestmark = pytest .mark .django_db
1211
12+ @pytest .fixture (autouse = True )
13+ def test_router ():
14+ import django_github_app .views
15+ from django_github_app .routing import GitHubRouter
1316
14- class TestView (BaseWebhookView [SyncGitHubAPI ]):
17+ old_routers = GitHubRouter ._routers .copy ()
18+ GitHubRouter ._routers = []
19+
20+ old_router = django_github_app .views ._router
21+
22+ test_router = GitHubRouter ()
23+ django_github_app .views ._router = test_router
24+
25+ yield test_router
26+
27+ GitHubRouter ._routers = old_routers
28+ django_github_app .views ._router = old_router
29+
30+
31+ class FixedTestView (BaseWebhookView [SyncGitHubAPI ]):
1532 github_api_class = SyncGitHubAPI
1633
1734 def post (self , request : HttpRequest ) -> JsonResponse :
@@ -21,10 +38,9 @@ def post(self, request: HttpRequest) -> JsonResponse:
2138class BrokenTestView (BaseWebhookView [SyncGitHubAPI ]):
2239 github_api_class = SyncGitHubAPI
2340
24- # Intentionally break the singleton pattern to demonstrate memory issue
2541 @property
2642 def router (self ) -> GitHubRouter :
27- # Always create a new router (simulating the original issue)
43+ # Always create a new router (simulating issue #73 )
2844 return GitHubRouter (* GitHubRouter .routers )
2945
3046 def post (self , request : HttpRequest ) -> JsonResponse :
@@ -33,117 +49,55 @@ def post(self, request: HttpRequest) -> JsonResponse:
3349
3450class TestGitHubRouter :
3551 def test_router_single_instance (self ):
36- """Test that router is instantiated only once per view class."""
37- # Reset the router
38- TestView ._router = None
39-
40- # Create multiple views
41- view1 = TestView ()
42- view2 = TestView ()
52+ view1 = FixedTestView ()
53+ view2 = FixedTestView ()
4354
44- # Get router from both instances
4555 router1 = view1 .router
4656 router2 = view2 .router
4757
48- # Verify they are the same object
4958 assert router1 is router2
50-
51- # Verify subsequent calls return the same instance
5259 assert view1 .router is router1
5360 assert view2 .router is router2
5461
55- def test_router_multiple_instances_without_fix (self ):
56- """Test demonstrates that without the fix, each view gets a new router instance.
62+ def test_duplicate_routers_without_module_level_router (self ):
63+ view_count = 5
5764
58- This test shows that the problem that was fixed (creating a new router on each access)
59- would result in multiple router instances. This test validates the behavior without
60- using memray to measure memory usage.
61- """
62- # Create views with the broken implementation that creates new routers
6365 views = []
6466 routers = []
6567
66- # Create just a few views - enough to demonstrate the issue
67- for _ in range (5 ):
68+ for _ in range (view_count ):
6869 view = BrokenTestView ()
69- # Get a router reference from each view
70- router = view .router
7170 views .append (view )
72- routers .append (router )
71+ routers .append (view . router )
7372
74- # Ensure we have the expected number of views
75- assert len (views ) == 5
73+ assert len (views ) == view_count
7674
77- # Verify that without the fix, each view gets a unique router instance
7875 unique_router_count = len (set (id (r ) for r in routers ))
79- assert unique_router_count == 5 , (
80- f"Expected 5 unique routers, got { unique_router_count } "
81- )
76+ assert unique_router_count == view_count
8277
83- def test_fix_prevents_duplicate_routers (self ):
84- """Test that the fix prevents duplication of routers even when accessed multiple times.
78+ def test_no_duplicate_routers (self ):
79+ router_ids = set ()
8580
86- This test simulates accessing the router multiple times from different views,
87- which is what happens in a high-traffic application with many requests.
88- """
89- # Reset the router
90- TestView ._router = None
91-
92- # Create a reference to the first router
93- view1 = TestView ()
94- first_router = view1 .router
95-
96- # Track the number of unique router instances
97- router_ids = {id (first_router )}
98-
99- # Simulate multiple requests by creating views and accessing their router repeatedly
10081 for _ in range (100 ):
101- view = TestView ()
82+ view = FixedTestView ()
10283
103- # Access the router multiple times (simulating multiple uses per request)
84+ # really goose it up with adding duplicate routers
10485 for _ in range (10 ):
105- router = view .router
106- router_ids .add (id (router ))
86+ router_ids .add (id (view .router ))
10787
108- # With the fix, we should have exactly ONE router instance despite
88+ # we should have exactly ONE router instance despite
10989 # creating it 100 views x 10 accesses = 1000 times
110- assert len (router_ids ) == 1 , (
111- f"Expected 1 unique router ID, got { len (router_ids )} "
112- )
113-
114- @pytest .mark .limit_memory ("10MB" )
115- def test_router_memory_with_fix (self ):
116- """Test memory usage with the fix in place.
90+ assert len (router_ids ) == 1
11791
118- This test creates a massive number of views with the fixed implementation,
119- which should use minimal memory since all views share the same router.
120- """
121- # Reset the router
122- TestView ._router = None
92+ @pytest .mark .limit_memory ("2.5MB" )
93+ def test_router_memory_stress_test (self ):
94+ view_count = 50000
12395
124- # Create an extremely large number of views to really stress test the system
125- view_count = 5000 # Still a lot of views but not so many that it hangs
126-
127- # Store all views in memory to prevent garbage collection
128- # This gives us a more realistic picture of memory usage
12996 views = []
13097
131- # Create many views and access their routers
132- for i in range (view_count ):
133- view = TestView ()
134- # Access the router property to trigger creation if not already created
135- router = view .router
136-
137- # Store view in list to prevent garbage collection
98+ for _ in range (view_count ):
99+ view = FixedTestView ()
138100 views .append (view )
139101
140- # Only store a reference to the first router to compare later
141- if i == 0 :
142- first_router = router
143-
144- # Ensure we created the expected number of views
145102 assert len (views ) == view_count
146-
147- # Verify all views share the same router instance
148- for view in views :
149- assert view .router is first_router
103+ assert all (view .router is views [0 ].router for view in views )
0 commit comments