From e1eddb401df713408ac88644fadaa8f79e5c9660 Mon Sep 17 00:00:00 2001 From: Yuqian Li Date: Fri, 11 Sep 2020 16:20:03 -0700 Subject: [PATCH] Revert the revert (#65602) --- .../complex_layout/android/settings.gradle | 10 + dev/benchmarks/complex_layout/ios/.gitignore | 32 ++ dev/benchmarks/complex_layout/ios/Podfile | 41 +++ .../ios/Runner/AppDelegate.swift | 17 + .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../LaunchImage.imageset/Contents.json | 23 ++ .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../ios/Runner/Runner-Bridging-Header.h | 5 + dev/benchmarks/complex_layout/lib/main.dart | 1 + dev/benchmarks/complex_layout/pubspec.yaml | 5 +- .../test/measure_scroll_smoothness.dart | 296 ++++++++++++++++++ .../measure_scroll_smoothness_test.dart | 17 + ...lex_layout_android__scroll_smoothness.dart | 14 + dev/devicelab/lib/tasks/perf_tests.dart | 48 +++ dev/devicelab/manifest.yaml | 8 + packages/flutter_test/lib/src/binding.dart | 1 - 22 files changed, 520 insertions(+), 3 deletions(-) create mode 100644 dev/benchmarks/complex_layout/ios/.gitignore create mode 100644 dev/benchmarks/complex_layout/ios/Podfile create mode 100644 dev/benchmarks/complex_layout/ios/Runner/AppDelegate.swift create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 dev/benchmarks/complex_layout/ios/Runner/Runner-Bridging-Header.h create mode 100644 dev/benchmarks/complex_layout/test/measure_scroll_smoothness.dart create mode 100644 dev/benchmarks/complex_layout/test_driver/measure_scroll_smoothness_test.dart create mode 100644 dev/devicelab/bin/tasks/complex_layout_android__scroll_smoothness.dart diff --git a/dev/benchmarks/complex_layout/android/settings.gradle b/dev/benchmarks/complex_layout/android/settings.gradle index 663db3adba..d3b6a4013d 100644 --- a/dev/benchmarks/complex_layout/android/settings.gradle +++ b/dev/benchmarks/complex_layout/android/settings.gradle @@ -3,3 +3,13 @@ // found in the LICENSE file. include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/dev/benchmarks/complex_layout/ios/.gitignore b/dev/benchmarks/complex_layout/ios/.gitignore new file mode 100644 index 0000000000..e96ef602b8 --- /dev/null +++ b/dev/benchmarks/complex_layout/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/dev/benchmarks/complex_layout/ios/Podfile b/dev/benchmarks/complex_layout/ios/Podfile new file mode 100644 index 0000000000..1e8c3c90a5 --- /dev/null +++ b/dev/benchmarks/complex_layout/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/dev/benchmarks/complex_layout/ios/Runner/AppDelegate.swift b/dev/benchmarks/complex_layout/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000000..d815fed684 --- /dev/null +++ b/dev/benchmarks/complex_layout/ios/Runner/AppDelegate.swift @@ -0,0 +1,17 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_Px$?ny*JR5%f>l)FnDQ543{x%ZCiu33$Wg!pQFfT_}?5Q|_VSlIbLC`dpoMXL}9 zHfd9&47Mo(7D231gb+kjFxZHS4-m~7WurTH&doVX2KI5sU4v(sJ1@T9eCIKPjsqSr z)C01LsCxk=72-vXmX}CQD#BD;Cthymh&~=f$Q8nn0J<}ZrusBy4PvRNE}+1ceuj8u z0mW5k8fmgeLnTbWHGwfKA3@PdZxhn|PypR&^p?weGftrtCbjF#+zk_5BJh7;0`#Wr zgDpM_;Ax{jO##IrT`Oz;MvfwGfV$zD#c2xckpcXC6oou4ML~ezCc2EtnsQTB4tWNg z?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso9Ityq$m^S>15>-?0(zQ<8Qy<_TjHE33(?_M8oaM zyc;NxzRVK@DL6RJnX%U^xW0Gpg(lXp(!uK1v0YgHjs^ZXSQ|m#lV7ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000000..89c2725b70 --- /dev/null +++ b/dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/dev/benchmarks/complex_layout/ios/Runner/Runner-Bridging-Header.h b/dev/benchmarks/complex_layout/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000000..95b7baf386 --- /dev/null +++ b/dev/benchmarks/complex_layout/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "GeneratedPluginRegistrant.h" diff --git a/dev/benchmarks/complex_layout/lib/main.dart b/dev/benchmarks/complex_layout/lib/main.dart index 4496bdd56a..69d6b133f7 100644 --- a/dev/benchmarks/complex_layout/lib/main.dart +++ b/dev/benchmarks/complex_layout/lib/main.dart @@ -109,6 +109,7 @@ class ComplexLayoutState extends State { Expanded( child: ListView.builder( key: const Key('complex-scroll'), // this key is used by the driver test + controller: ScrollController(), // So that the scroll offset can be tracked itemBuilder: (BuildContext context, int index) { if (index % 2 == 0) return FancyImageItem(index, key: PageStorageKey(index)); diff --git a/dev/benchmarks/complex_layout/pubspec.yaml b/dev/benchmarks/complex_layout/pubspec.yaml index e0616ea68c..ac23c61e1c 100644 --- a/dev/benchmarks/complex_layout/pubspec.yaml +++ b/dev/benchmarks/complex_layout/pubspec.yaml @@ -3,7 +3,7 @@ description: A benchmark of a relatively complex layout. environment: # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. - sdk: ">=2.0.0-dev.68.0 <3.0.0" + sdk: ">=2.2.2 <3.0.0" dependencies: flutter: @@ -46,6 +46,7 @@ dev_dependencies: flutter_test: sdk: flutter test: 1.16.0-nullsafety.4 + e2e: 0.7.0 _fe_analyzer_shared: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 0.39.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -90,4 +91,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: 4f3b +# PUBSPEC CHECKSUM: fe86 diff --git a/dev/benchmarks/complex_layout/test/measure_scroll_smoothness.dart b/dev/benchmarks/complex_layout/test/measure_scroll_smoothness.dart new file mode 100644 index 0000000000..05c5107bfd --- /dev/null +++ b/dev/benchmarks/complex_layout/test/measure_scroll_smoothness.dart @@ -0,0 +1,296 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This test is a use case of flutter/flutter#60796 +// the test should be run as: +// flutter drive -t test/using_array.dart --driver test_driver/scrolling_test_e2e_test.dart + +import 'dart:ui' as ui; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:e2e/e2e.dart'; + +import 'package:complex_layout/main.dart' as app; + +class PointerDataTestBinding extends E2EWidgetsFlutterBinding { + // PointerData injection would usually be considered device input and therefore + // blocked by [TestWidgetsFlutterBinding]. Override this behavior + // to help events go into widget tree. + @override + void dispatchEvent( + PointerEvent event, + HitTestResult hitTestResult, { + TestBindingEventSource source = TestBindingEventSource.device, + }) { + super.dispatchEvent(event, hitTestResult, source: TestBindingEventSource.test); + } +} + +/// A union of [ui.PointerDataPacket] and the time it should be sent. +class PointerDataRecord { + PointerDataRecord(this.timeStamp, List data) + : data = ui.PointerDataPacket(data: data); + final ui.PointerDataPacket data; + final Duration timeStamp; +} + +/// Generates the [PointerDataRecord] to simulate a drag operation from +/// `center - totalMove/2` to `center + totalMove/2`. +Iterable dragInputDatas( + final Duration epoch, + final Offset center, { + final Offset totalMove = const Offset(0, -400), + final Duration totalTime = const Duration(milliseconds: 2000), + final double frequency = 90, +}) sync* { + final Offset startLocation = (center - totalMove / 2) * ui.window.devicePixelRatio; + // The issue is about 120Hz input on 90Hz refresh rate device. + // We test 90Hz input on 60Hz device here, which shows similar pattern. + final int moveEventCount = totalTime.inMicroseconds * frequency ~/ const Duration(seconds: 1).inMicroseconds; + final Offset movePerEvent = totalMove / moveEventCount.toDouble() * ui.window.devicePixelRatio; + yield PointerDataRecord(epoch, [ + ui.PointerData( + timeStamp: epoch, + change: ui.PointerChange.add, + physicalX: startLocation.dx, + physicalY: startLocation.dy, + ), + ui.PointerData( + timeStamp: epoch, + change: ui.PointerChange.down, + physicalX: startLocation.dx, + physicalY: startLocation.dy, + pointerIdentifier: 1, + ), + ]); + for (int t = 0; t < moveEventCount + 1; t++) { + final Offset position = startLocation + movePerEvent * t.toDouble(); + yield PointerDataRecord( + epoch + totalTime * t ~/ moveEventCount, + [ui.PointerData( + timeStamp: epoch + totalTime * t ~/ moveEventCount, + change: ui.PointerChange.move, + physicalX: position.dx, + physicalY: position.dy, + // Scrolling behavior depends on this delta rather + // than the position difference. + physicalDeltaX: movePerEvent.dx, + physicalDeltaY: movePerEvent.dy, + pointerIdentifier: 1, + )], + ); + } + final Offset position = startLocation + totalMove; + yield PointerDataRecord(epoch + totalTime, [ui.PointerData( + timeStamp: epoch + totalTime, + change: ui.PointerChange.up, + physicalX: position.dx, + physicalY: position.dy, + pointerIdentifier: 1, + )]); +} + +enum TestScenario { + resampleOn90Hz, + resampleOn59Hz, + resampleOff90Hz, + resampleOff59Hz, +} + +class ResampleFlagVariant extends TestVariant { + ResampleFlagVariant(this.binding); + final E2EWidgetsFlutterBinding binding; + + @override + final Set values = Set.from(TestScenario.values); + + TestScenario currentValue; + bool get resample { + switch(currentValue) { + case TestScenario.resampleOn90Hz: + case TestScenario.resampleOn59Hz: + return true; + case TestScenario.resampleOff90Hz: + case TestScenario.resampleOff59Hz: + return false; + } + throw ArgumentError; + } + double get frequency { + switch(currentValue) { + case TestScenario.resampleOn90Hz: + case TestScenario.resampleOff90Hz: + return 90.0; + case TestScenario.resampleOn59Hz: + case TestScenario.resampleOff59Hz: + return 59.0; + } + throw ArgumentError; + } + + Map result; + + @override + String describeValue(TestScenario value) { + switch(value) { + case TestScenario.resampleOn90Hz: + return 'resample on with 90Hz input'; + case TestScenario.resampleOn59Hz: + return 'resample on with 59Hz input'; + case TestScenario.resampleOff90Hz: + return 'resample off with 90Hz input'; + case TestScenario.resampleOff59Hz: + return 'resample off with 59Hz input'; + } + throw ArgumentError; + } + + @override + Future setUp(TestScenario value) async { + currentValue = value; + final bool original = binding.resamplingEnabled; + binding.resamplingEnabled = resample; + return original; + } + + @override + Future tearDown(TestScenario value, bool memento) async { + binding.resamplingEnabled = memento; + binding.reportData[describeValue(value)] = result; + } +} + +Future main() async { + final PointerDataTestBinding binding = PointerDataTestBinding(); + assert(WidgetsBinding.instance == binding); + binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive; + binding.reportData ??= {}; + final ResampleFlagVariant variant = ResampleFlagVariant(binding); + testWidgets('Smoothness test', (WidgetTester tester) async { + app.main(); + await tester.pumpAndSettle(); + final Finder scrollerFinder = find.byKey(const ValueKey('complex-scroll')); + final ListView scroller = tester.widget(scrollerFinder); + final ScrollController controller = scroller.controller; + final List frameTimestamp = []; + final List scrollOffset = []; + final List delays = []; + binding.addPersistentFrameCallback((Duration timeStamp) { + if (controller.hasClients) { + // This if is necessary because by the end of the test the widget tree + // is destroyed. + frameTimestamp.add(timeStamp.inMicroseconds); + scrollOffset.add(controller.offset); + } + }); + + Duration now() => binding.currentSystemFrameTimeStamp; + Future scroll() async { + // Extra 50ms to avoid timeouts. + final Duration startTime = const Duration(milliseconds: 500) + now(); + for (final PointerDataRecord record in dragInputDatas( + startTime, + tester.getCenter(scrollerFinder), + frequency: variant.frequency, + )) { + await tester.binding.delayed(record.timeStamp - now()); + // This now measures how accurate the above delayed is. + final Duration delay = now() - record.timeStamp; + if (delays.length < frameTimestamp.length) { + while (delays.length < frameTimestamp.length - 1) { + delays.add(Duration.zero); + } + delays.add(delay); + } else if (delays.last < delay) { + delays.last = delay; + } + ui.window.onPointerDataPacket(record.data); + } + } + + for (int n = 0; n < 5; n++) { + await scroll(); + } + variant.result = scrollSummary(scrollOffset, delays, frameTimestamp); + await tester.pumpAndSettle(); + scrollOffset.clear(); + delays.clear(); + await tester.idle(); + }, semanticsEnabled: false, variant: variant); +} + +/// Calculates the smoothness measure from `scrollOffset` and `delays` list. +/// +/// Smoothness (`abs_jerk`) is measured by the absolute value of the discrete +/// 2nd derivative of the scroll offset. +/// +/// It was experimented that jerk (3rd derivative of the position) is a good +/// measure the smoothness. +/// Here we are using 2nd derivative instead because the input is completely +/// linear and the expected acceleration should be strictly zero. +/// Observed acceleration is jumping from positive to negative within +/// adjacent frames, meaning mathematically the discrete 3-rd derivative +/// (`f[3] - 3*f[2] + 3*f[1] - f[0]`) is not a good approximation of jerk +/// (continuous 3-rd derivative), while discrete 2nd +/// derivative (`f[2] - 2*f[1] + f[0]`) on the other hand is a better measure +/// of how the scrolling deviate away from linear, and given the acceleration +/// should average to zero within two frames, it's also a good approximation +/// for jerk in terms of physics. +/// We use abs rather than square because square (2-norm) amplifies the +/// effect of the data point that's relatively large, but in this metric +/// we prefer smaller data point to have similar effect. +/// This is also why we count the number of data that's larger than a +/// threshold (and the result is tested not sensitive to this threshold), +/// which is effectively a 0-norm. +/// +/// Frames that are too slow to build (longer than 40ms) or with input delay +/// longer than 16ms (1/60Hz) is filtered out to separate the janky due to slow +/// response. +/// +/// The returned map has keys: +/// `average_abs_jerk`: average for the overall smoothness. +/// `janky_count`: number of frames with `abs_jerk` larger than 0.5. +/// `dropped_frame_count`: number of frames that are built longer than 40ms and +/// are not used for smoothness measurement. +/// `frame_timestamp`: the list of the timestamp for each frame, in the time +/// order. +/// `scroll_offset`: the scroll offset for each frame. Its length is the same as +/// `frame_timestamp`. +/// `input_delay`: the list of maximum delay time of the input simulation during +/// a frame. Its length is the same as `frame_timestamp` +Map scrollSummary( + List scrollOffset, + List delays, + List frameTimestamp, +) { + double jankyCount = 0; + double absJerkAvg = 0; + int lostFrame = 0; + for (int i = 1; i < scrollOffset.length-1; i += 1) { + if (frameTimestamp[i+1] - frameTimestamp[i-1] > 40E3 || + (i >= delays.length || delays[i] > const Duration(milliseconds: 16))) { + // filter data points from slow frame building or input simulation artifact + lostFrame += 1; + continue; + } + // + final double absJerk = (scrollOffset[i-1] + scrollOffset[i+1] - 2*scrollOffset[i]).abs(); + absJerkAvg += absJerk; + if (absJerk > 0.5) + jankyCount += 1; + } + // expect(lostFrame < 0.1 * frameTimestamp.length, true); + absJerkAvg /= frameTimestamp.length - lostFrame; + + return { + 'janky_count': jankyCount, + 'average_abs_jerk': absJerkAvg, + 'dropped_frame_count': lostFrame, + 'frame_timestamp': List.from(frameTimestamp), + 'scroll_offset': List.from(scrollOffset), + 'input_delay': delays.map((Duration data) => data.inMicroseconds).toList(), + }; +} diff --git a/dev/benchmarks/complex_layout/test_driver/measure_scroll_smoothness_test.dart b/dev/benchmarks/complex_layout/test_driver/measure_scroll_smoothness_test.dart new file mode 100644 index 0000000000..fd29ebe6a9 --- /dev/null +++ b/dev/benchmarks/complex_layout/test_driver/measure_scroll_smoothness_test.dart @@ -0,0 +1,17 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:e2e/e2e_driver.dart' as driver; + +Future main() => driver.e2eDriver( + timeout: const Duration(minutes: 5), + responseDataCallback: (Map data) async { + await driver.writeResponseData( + data, + testOutputFilename: 'scroll_smoothness_test', + ); + } +); diff --git a/dev/devicelab/bin/tasks/complex_layout_android__scroll_smoothness.dart b/dev/devicelab/bin/tasks/complex_layout_android__scroll_smoothness.dart new file mode 100644 index 0000000000..960965e2a7 --- /dev/null +++ b/dev/devicelab/bin/tasks/complex_layout_android__scroll_smoothness.dart @@ -0,0 +1,14 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter_devicelab/tasks/perf_tests.dart'; +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createsScrollSmoothnessPerfTest()); +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index 33f819e5e7..cab549dda0 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -298,6 +298,54 @@ TaskFunction createsMultiWidgetConstructPerfE2ETest() { ).run; } +TaskFunction createsScrollSmoothnessPerfTest() { + final String testDirectory = + '${flutterDirectory.path}/dev/benchmarks/complex_layout'; + const String testTarget = 'test/measure_scroll_smoothness.dart'; + return () { + return inDirectory(testDirectory, () async { + final Device device = await devices.workingDevice; + await device.unlock(); + final String deviceId = device.deviceId; + await flutter('packages', options: ['get']); + + await flutter('drive', options: [ + '-v', + '--verbose-system-logs', + '--profile', + '-t', testTarget, + '-d', + deviceId, + ]); + final Map data = json.decode( + file('$testDirectory/build/scroll_smoothness_test.json').readAsStringSync(), + ) as Map; + + final Map result = {}; + void addResult(dynamic data, String suffix) { + assert(data is Map); + const List metricKeys = [ + 'janky_count', + 'average_abs_jerk', + 'dropped_frame_count', + ]; + for (final String key in metricKeys) { + result[key+suffix] = data[key]; + } + } + addResult(data['resample on with 90Hz input'], '_with_resampler_90Hz'); + addResult(data['resample on with 59Hz input'], '_with_resampler_59Hz'); + addResult(data['resample off with 90Hz input'], '_without_resampler_90Hz'); + addResult(data['resample off with 59Hz input'], '_without_resampler_59Hz'); + + return TaskResult.success( + result, + benchmarkScoreKeys: result.keys.toList(), + ); + }); + }; +} + TaskFunction createFramePolicyIntegrationTest() { final String testDirectory = '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks'; diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 89075b38b8..5d3a10ef17 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -114,6 +114,14 @@ tasks: # Android on-device tests + complex_layout_android__scroll_smoothness: + description: > + Measures the smoothness of scrolling of the Complex Layout sample app on + Android. + stage: devicelab + required_agent_capabilities: ["linux/android"] + flaky: true + complex_layout_scroll_perf__timeline_summary: description: > Measures the runtime performance of the Complex Layout sample app on diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index fe65b2de26..dad55ba775 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -1504,7 +1504,6 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { renderView._pointers[event.pointer].decay = _kPointerDecay; _handleViewNeedsPaint(); } else if (event.down) { - assert(event is PointerDownEvent); renderView._pointers[event.pointer] = _LiveTestPointerRecord( event.pointer, event.position,