// Copyright (c) 2020 Proton Technologies AG // // This file is part of ProtonMail Bridge.Bridge. // // ProtonMail Bridge is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ProtonMail Bridge is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . package benchmarks import ( "strings" "testing" "time" "github.com/ProtonMail/proton-bridge/test/context" "github.com/ProtonMail/proton-bridge/test/mocks" ) func benchTestContext() (*context.TestContext, *mocks.IMAPClient) { ctx := context.New("bridge") username := "user" account := ctx.GetTestAccount(username) if account == nil { panic("account " + username + " does not exist") } _ = ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled()) if err := ctx.LoginUser(account.Username(), account.Password(), account.MailboxPassword()); err != nil { panic(err) } imapClient := ctx.GetIMAPClient("client") imapClient.Login(account.Address(), account.BridgePassword()) // waitForSync between bridge and API. There is no way to know precisely // from the outside when the bridge is synced. We could wait for first // response from any fetch, but we don't know how many messages should be // there. Unless we hard code the number of messages. // Please, check this time is enough when doing benchmarks and don't forget // to exclude this time from total time. time.Sleep(10 * time.Second) return ctx, imapClient } func BenchmarkIMAPFetch(b *testing.B) { tc, c := benchTestContext() defer tc.Cleanup() c.Select("All Mail").AssertOK() fetchBench := []struct{ ids, args string }{ {"1:10", "rfc822.size"}, {"1:100", "rfc822.size"}, {"1:1000", "rfc822.size"}, {"1:*", "rfc822.size"}, } for _, bd := range fetchBench { ids, args := bd.ids, bd.args // pin b.Run(ids+"-"+args, func(b *testing.B) { for i := 0; i < b.N; i++ { c.Fetch(ids, args) } }) } } func BenchmarkCachingFetch(b *testing.B) { tc, c := benchTestContext() defer tc.Cleanup() c.Select("\"All Mail\"").AssertOK() ids := "1:100" args := "body.peek[]" tries := []string{"long", "short"} for _, try := range tries { b.Run(strings.Join([]string{ids, args, try}, "-"), func(b *testing.B) { for i := 0; i < b.N; i++ { c.Fetch(ids, args) } }) } } func BenchmarkIMAPAppleMail(b *testing.B) { tc, c := benchTestContext() defer tc.Cleanup() // assume we have at least 50 messages in INBOX idRange := "1:50" newUID := "50" // assume that Apple mail don't know about this mail // I will use raw send command to completely reproduce the calls // (including quotation and case sensitivity) b.Run("default", func(b *testing.B) { for i := 0; i < b.N; i++ { for _, command := range []string{ "CAPABILITY", "ID (" + `"name" "Mac OS X Mail" ` + `"version" "11.5 (3445.9.1)" ` + `"os" "Mac OS X" ` + `"os-version" "10.13.6 (17G3025)" ` + `"vendor" "Apple Inc."` + ")", `LIST "" ""`, `STATUS INBOX (MESSAGES UIDNEXT UIDVALIDITY UNSEEN)`, `SELECT INBOX`, `FETCH ` + idRange + ` (FLAGS UID)`, `FETCH ` + idRange + " " + `(` + `INTERNALDATE UID RFC822.SIZE FLAGS ` + `BODY.PEEK[` + `HEADER.FIELDS (` + `date subject from to cc message-id in-reply-to references ` + `x-priority x-uniform-type-identifier x-universally-unique-identifier ` + `list-id list-unsubscribe` + `)])`, `UID FETCH ` + newUID + ` (BODYSTRUCTURE BODY.PEEK[HEADER])`, // if email has attachment it is splitted to several fetches //`UID FETCH 133 (BODY.PEEK[3]<0.5877469> BODY.PEEK[1] BODY.PEEK[2])`, //`UID FETCH 133 BODY.PEEK[3]<5877469.2925661>`, // here I will just use section download, which is used by AppleMail `UID FETCH ` + newUID + ` BODY.PEEK[1]`, // here I will just use partial download, which is used by AppleMail `UID FETCH ` + newUID + ` BODY.PEEK[]<0.2000>`, } { c.SendCommand(command).AssertOK() } } }) } func BenchmarkIMAPOutlook(b *testing.B) { tc, c := benchTestContext() defer tc.Cleanup() // assume we have at least 50 messages in INBOX idRange := "1:50" // I will use raw send command to completely reproduce the calls // (including quotation and case sensitivity) b.Run("default", func(b *testing.B) { for i := 0; i < b.N; i++ { for _, command := range []string{ /* "ID ("+ `"name" "Mac OS X Mail" `+ `"version" "11.5 (3445.9.1)" `+ `"os" "Mac OS X" `+ `"os-version" "10.13.6 (17G3025)" `+ `"vendor" "Apple Inc."`+ ")", */ `SELECT "INBOX"`, `UID SEARCH ` + idRange + ` SINCE 01-Sep-2019`, `UID FETCH 1:* (UID FLAGS)`, `UID FETCH ` + idRange + ` (UID FLAGS RFC822.SIZE BODY.PEEK[] INTERNALDATE)`, } { c.SendCommand(command).AssertOK() } } }) } func BenchmarkIMAPThunderbird(b *testing.B) { tc, c := benchTestContext() defer tc.Cleanup() // I will use raw send command to completely reproduce the calls // (including quotation and case sensitivity) b.Run("default", func(b *testing.B) { for i := 0; i < b.N; i++ { for _, command := range []string{ `capability`, `ID ("name" "Thunderbird" "version" "68.2.0")`, `select "INBOX"`, `getquotaroot "INBOX"`, `UID fetch 1:* (FLAGS)`, } { c.SendCommand(command).AssertOK() } } }) }