From 00175bd0965c4764333a227478d1db280e04574c Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Wed, 6 Aug 2025 03:04:02 -0700 Subject: [PATCH] Compare nightly results up to 7 days in the back (#53065) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53065 Instead of checking results from the previous day, allow to go back up to 7 days in the past to check for previous runs. Changelog: [Internal] [Changed] - Reviewed By: cipolleschi Differential Revision: D79568067 fbshipit-source-id: 5d6fac6a06b4e26e52de6fbf6187ec8f8e44e10e --- .../__tests__/firebaseUtils-test.js | 280 ++++++++++++++++++ .../collectNightlyOutcomes.js | 30 +- .github/workflow-scripts/firebaseUtils.js | 42 +++ 3 files changed, 343 insertions(+), 9 deletions(-) diff --git a/.github/workflow-scripts/__tests__/firebaseUtils-test.js b/.github/workflow-scripts/__tests__/firebaseUtils-test.js index 4b1c5f67e3f..a3104699e29 100644 --- a/.github/workflow-scripts/__tests__/firebaseUtils-test.js +++ b/.github/workflow-scripts/__tests__/firebaseUtils-test.js @@ -362,6 +362,286 @@ describe('FirebaseClient', () => { ); }); }); + + describe('getLatestResults', () => { + it('should authenticate before making requests if no token exists', async () => { + const authResponse = {idToken: 'new-token'}; + const mockResults = [{library: 'test', status: 'success'}]; + + global.fetch + .mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)), + }) + .mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)), + }); + + const client = new FirebaseClient(); + const result = await client.getLatestResults('2023-12-15', 1); + + expect(result).toEqual({ + results: mockResults, + date: '2023-12-14', + }); + expect(client.idToken).toBe('new-token'); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-12-14 (1 days back)...', + ); + expect(console.log).toHaveBeenCalledWith( + 'Found results from 2023-12-14 (1 days back)', + ); + }); + + it('should find results from the previous day', async () => { + const mockResults = [{library: 'test', status: 'success'}]; + global.fetch.mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + const result = await client.getLatestResults('2023-12-15', 7); + + expect(result).toEqual({ + results: mockResults, + date: '2023-12-14', + }); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-12-14 (1 days back)...', + ); + expect(console.log).toHaveBeenCalledWith( + 'Found results from 2023-12-14 (1 days back)', + ); + }); + + it('should find results from several days back', async () => { + const mockResults = [{library: 'test', status: 'success'}]; + + // Mock 404 responses for first 2 days, then success on 3rd day + global.fetch + .mockResolvedValueOnce({ + ok: false, + status: 404, + text: jest.fn().mockResolvedValueOnce('Not Found'), + }) + .mockResolvedValueOnce({ + ok: false, + status: 404, + text: jest.fn().mockResolvedValueOnce('Not Found'), + }) + .mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + const result = await client.getLatestResults('2023-12-15', 7); + + expect(result).toEqual({ + results: mockResults, + date: '2023-12-12', + }); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-12-14 (1 days back)...', + ); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-12-13 (2 days back)...', + ); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-12-12 (3 days back)...', + ); + expect(console.log).toHaveBeenCalledWith( + 'Found results from 2023-12-12 (3 days back)', + ); + }); + + it('should skip empty results and continue searching', async () => { + const mockResults = [{library: 'test', status: 'success'}]; + + // Mock empty array for first day, then valid results on second day + global.fetch + .mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify([])), + }) + .mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + const result = await client.getLatestResults('2023-12-15', 7); + + expect(result).toEqual({ + results: mockResults, + date: '2023-12-13', + }); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-12-14 (1 days back)...', + ); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-12-13 (2 days back)...', + ); + expect(console.log).toHaveBeenCalledWith( + 'Found results from 2023-12-13 (2 days back)', + ); + }); + + it('should return null when no results found within maxDaysBack', async () => { + // Mock 404 responses for all days + global.fetch.mockResolvedValue({ + ok: false, + status: 404, + text: jest.fn().mockResolvedValue('Not Found'), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + const result = await client.getLatestResults('2023-12-15', 3); + + expect(result).toEqual({ + results: null, + date: null, + }); + expect(console.log).toHaveBeenCalledWith( + 'No previous results found within the last 3 days', + ); + expect(global.fetch).toHaveBeenCalledTimes(3); + }); + + it('should use default maxDaysBack of 7 when not specified', async () => { + // Mock 404 responses for all days + global.fetch.mockResolvedValue({ + ok: false, + status: 404, + text: jest.fn().mockResolvedValue('Not Found'), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + const result = await client.getLatestResults('2023-12-15'); + + expect(result).toEqual({ + results: null, + date: null, + }); + expect(console.log).toHaveBeenCalledWith( + 'No previous results found within the last 7 days', + ); + expect(global.fetch).toHaveBeenCalledTimes(7); + }); + + it('should handle non-404 errors and continue searching', async () => { + const mockResults = [{library: 'test', status: 'success'}]; + + // Mock 500 error for first day, then success on second day + global.fetch + .mockResolvedValueOnce({ + ok: false, + status: 500, + text: jest.fn().mockResolvedValueOnce('Internal Server Error'), + }) + .mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + const result = await client.getLatestResults('2023-12-15', 7); + + expect(result).toEqual({ + results: mockResults, + date: '2023-12-13', + }); + expect(console.log).toHaveBeenCalledWith( + 'No results found for 2023-12-14: HTTP 500: Internal Server Error', + ); + expect(console.log).toHaveBeenCalledWith( + 'Found results from 2023-12-13 (2 days back)', + ); + }); + + it('should handle date boundaries correctly', async () => { + const mockResults = [{library: 'test', status: 'success'}]; + global.fetch.mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + // Test month boundary + const result = await client.getLatestResults('2023-12-01', 1); + + expect(result).toEqual({ + results: mockResults, + date: '2023-11-30', + }); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-11-30 (1 days back)...', + ); + }); + + it('should handle year boundary correctly', async () => { + const mockResults = [{library: 'test', status: 'success'}]; + global.fetch.mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + // Test year boundary + const result = await client.getLatestResults('2024-01-01', 1); + + expect(result).toEqual({ + results: mockResults, + date: '2023-12-31', + }); + expect(console.log).toHaveBeenCalledWith( + 'Checking for results on 2023-12-31 (1 days back)...', + ); + }); + + it('should handle null results and continue searching', async () => { + const mockResults = [{library: 'test', status: 'success'}]; + + // Mock null for first day, then valid results on second day + global.fetch + .mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce('null'), + }) + .mockResolvedValueOnce({ + ok: true, + text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)), + }); + + const client = new FirebaseClient(); + client.idToken = 'existing-token'; + + const result = await client.getLatestResults('2023-12-15', 7); + + expect(result).toEqual({ + results: mockResults, + date: '2023-12-13', + }); + }); + }); }); describe('compareResults', () => { diff --git a/.github/workflow-scripts/collectNightlyOutcomes.js b/.github/workflow-scripts/collectNightlyOutcomes.js index 8f83d07ff03..8771b8f549d 100644 --- a/.github/workflow-scripts/collectNightlyOutcomes.js +++ b/.github/workflow-scripts/collectNightlyOutcomes.js @@ -124,23 +124,35 @@ async function collectResults(discordWebHook) { // Initialize Firebase client const firebaseClient = new FirebaseClient(); const today = getTodayDate(); - const yesterday = getYesterdayDate(); try { // Store today's results in Firebase console.log(`Storing results for ${today} in Firebase...`); await firebaseClient.storeResults(today, outcomes); - // Get yesterday's results for comparison - console.log(`Retrieving results for ${yesterday} from Firebase...`); - const yesterdayResults = await firebaseClient.getResults(yesterday); + // Get the most recent previous results for comparison + console.log(`Looking for most recent previous results before ${today}...`); + const {results: previousResults, date: previousDate} = + await firebaseClient.getLatestResults(today); - // Compare results and identify broken/recovered tests - const {broken, recovered} = compareResults(outcomes, yesterdayResults); + let broken = []; + let recovered = []; - console.log( - `Found ${broken.length} newly broken tests and ${recovered.length} recovered tests`, - ); + if (previousResults) { + console.log(`Comparing with results from ${previousDate}`); + // Compare results and identify broken/recovered jobs + const comparison = compareResults(outcomes, previousResults); + broken = comparison.broken; + recovered = comparison.recovered; + + console.log( + `Found ${broken.length} newly broken jobs and ${recovered.length} recovered jobs compared to ${previousDate}`, + ); + } else { + console.log( + 'No previous results found for comparison - this might be the first run or no recent data available', + ); + } // Send comparison message to Discord if there are changes if (discordWebHook && (broken.length > 0 || recovered.length > 0)) { diff --git a/.github/workflow-scripts/firebaseUtils.js b/.github/workflow-scripts/firebaseUtils.js index 9340da9f21c..2e72dff7b75 100644 --- a/.github/workflow-scripts/firebaseUtils.js +++ b/.github/workflow-scripts/firebaseUtils.js @@ -86,6 +86,48 @@ class FirebaseClient { } } + /** + * Find the most recent available job results before the given date + * @param {string} currentDate - Current date in YYYY-MM-DD format + * @param {number} maxDaysBack - Maximum number of days to look back (default: 7) + * @returns {Promise<{results: Array|null, date: string|null}>} - Most recent results and their date + */ + async getLatestResults(currentDate, maxDaysBack = 7) { + if (!this.idToken) { + await this.authenticate(); + } + + const currentDateObj = new Date(currentDate); + + for (let daysBack = 1; daysBack <= maxDaysBack; daysBack++) { + const checkDate = new Date(currentDateObj); + checkDate.setDate(checkDate.getDate() - daysBack); + const checkDateStr = checkDate.toISOString().split('T')[0]; + + console.log( + `Checking for results on ${checkDateStr} (${daysBack} days back)...`, + ); + + try { + const results = await this.getResults(checkDateStr); + if (results && results.length > 0) { + console.log( + `Found results from ${checkDateStr} (${daysBack} days back)`, + ); + return {results, date: checkDateStr}; + } + } catch (error) { + console.log(`No results found for ${checkDateStr}: ${error.message}`); + continue; + } + } + + console.log( + `No previous results found within the last ${maxDaysBack} days`, + ); + return {results: null, date: null}; + } + async makeRequest(hostname, path, method, data = null) { const url = `https://${hostname}${path}`; const options = {