Feedback should be send to goran.milovanovic_ext@wikimedia.de.

0. Setup

Libraries setup.

# !diagnostics off
### --- Setup
knitr::opts_chunk$set(fig.width = 15, fig.height = 8) 
library(dplyr)
library(ggplot2)
library(data.table)
library(lawstat)
library(effsize)
library(TOSTER)
library(poweRlaw)
library(kableExtra)

0.1 Data sets

Load data sets.

donationsData <- fread('data/campaign-2017-donations.csv', 
                       header = T, data.table = F)
membershipData <- fread('data/campaign-2017-memberships.csv', 
                        header = T, data.table = F)

0.1 General considerations

The donations dataset

  • Refer to: https://phabricator.wikimedia.org/T194744
  • Goal: there are two pages: the “old” one, the “new” one; A/B test “old” vs. “new”
  • Dependent variable: donationsData$amount
  • Factor: in the donationsData$keyword, anything ending in “-ctrl” or “var”
  • The (re)coding schema for the donationsData$keyword factor is: https://phabricator.wikimedia.org/T194744#4207413
  • Rescale to yearly data by donationsData$interval (see the Membership dataset);
  • NOTE: “0” value for the donationsData$interval would mean: once per year.
  • Another dependent variable to look at: donationsData$opt_in.
  • DATA CLEANING: the donationsData$status:
  • Codes: B, N, X, Z
  • X == uncompleted, filter this out
  • B == booked, we got the money
  • N == booked, we got the money
  • Z = promise that they would send the money, filter this out

The membership dataset

  • The dependent variable is: membership_feee
  • Membership_fee_interval: the yearly fee can be paid once a year or every X month; the number in the data field signifies: 1 = booking every month, 3 = booking every three months, 6 = booking every six months 12 = booking every twelve months;
  • rescale all the membership fee observations to a one-year period.

1. Donations Data Set

1.1 Clean up

Experimental factor: in the donationsData$keyword, anything ending in “-ctrl” or “var”. Also, in in donationsData$status: filter out Z and X cases.

donationsData <- filter(donationsData, grepl("-ctrl$|var$", donationsData$keyword))
donationsData <- filter(donationsData, !(status %in% c('X', 'Z')))

1.2 Recode the experimental factor donationsData$keyword

  • See: https://phabricator.wikimedia.org/T194744#4207413
  • NOTE: In respect to the following comment:
  • “A special case is the last campaign on wikipedia.de (”wpde“).
  • It featured four banners and has therefore four keywords.
  • Every user in the campaign saw one large banner (“fulltop”) and after
  • that only small banners (“top”). So one can aggregate the data of
  • the large and small banners or treat it as two test campaigns."
  • The four paired banners for this campaign were treated as two banners only (old/new);
  • ratio: every user necessarily saw a pair of banners; makes no sense treating them as different factors.
recodingScheme <- read.csv('data/recodingScheme.csv', 
                           header = T,
                           stringsAsFactors = F,
                           check.names = F)
oldCodes <- unique(recodingScheme$`keyword of old lp`)
newCodes <- unique(recodingScheme$`keyword of new lp`)
donationsData$expFactor <- sapply(donationsData$keyword, function(x) {
  if (x %in% oldCodes) {
    return("old")
  } else if (x %in% newCodes) {
    return("new")
  } else {
    return("Unnasigned")
  }
})
# - filter out 'Unnasigned' codes:
donationsData <- filter(donationsData, !(expFactor == 'Unnasigned'))

1.3 Rescale amount to yearly data by interval

  • The yearly fee can be paid once a year or every X month; the number in the data field signifies X
  • 1 = booking every month
  • 3 = booking every three months
  • 6 = booking every six months
  • 12 = booking every twelve months
  • NOTE: “0” value for the donationsData$interval would mean: once per year
donationsData$amountYearly <- numeric(dim(donationsData)[1])
donationsData$amountYearly[donationsData$interval == 0 | donationsData$interval == 12] <- 
  donationsData$amount[donationsData$interval == 0 | donationsData$interval == 12]
donationsData$amountYearly[donationsData$interval == 1] <- 
  donationsData$amount[donationsData$interval == 1] * 12
donationsData$amountYearly[donationsData$interval == 3] <- 
  donationsData$amount[donationsData$interval == 3] * 4
donationsData$amountYearly[donationsData$interval == 6] <- 
  donationsData$amount[donationsData$interval == 6] * 2

1.4 Select only relevant variables

donationsData <- select(donationsData, 
                        campaign, expFactor, amountYearly, opt_in)

1.5 A/B testing (per campaign): Amount

Outline

The analytical workflow is as follows:

  • We always conduct a campaign-wise A/B test; so, each different method results in as many analyses as there were campaigns in the data set.
  • Method A. We run t-tests preceded by Levene’s test. If the Levene’s test implies the homogeneity of variance, we run independent t-tests, otherwise we run Welch t-tests.
  • Method B. Given that the sample sizes for the experimental A/B factor (“new”/“old”) are always unequal, we force Welch t-test procedure across all the campaigns.
  • Method C. We test for the power-law behavior in the dependent variable (amount of donation, campaign-wise and across the A/B levels) to find out whether the sampling distribution is Normal. Ratio: if the sampling distributions are not Normal (i.e. the distributions exhibit power-law behavior), we abandon the t-tests and look for non-parametric alternatives (Mann-Whitney U tests); if the sampling distributions are Normal, we can view extremely large donation amounts as outliers and repeat the t-tests following their removal. It turns out that in 3/4 of samples we can rule out power-laws (assuming x_min is the lowest observation at which the power-law behavior is asummed; tricky, we should re-run this w. previous estimation of x_min).
  • Method C1, C2. We perform t-tests according to the results of Levene’s tests (Method C1) or force Welch t-tests (Method C2) following the removal of the outliers.
  • Method D1, D2. Mann-Whitney U tests are performed as a non-parametric alternative, before the removal of outliers (Method D1) and following the removal of outliers (Method D2).
  • Conclusion. All methods converge to the same conclusion of no effect.

Method A. Run independent t-tests or Welch

Run independent t-tests or Welch, after testing for homogenity of variances w. Levene’s test, w. correction for unequal variances if necessary In other words, we first test for the homogeneity of variances, and the choose to conduct an Independent t-test in case of homogeneity or Welch in case of heterogeneity. No matter the nature of the t-test, we perform TOST equivalence testing against {.3, -.3} bounds considered as minimal effect size. The measure of effect size, however, is chosen in respect to the type of the t-test conducted (Cohen’s d or Glass’ delta).

Results. The following table summarizes the t-test results: campaign is the campaign’s code, n_old and n_new the number of observations for the “new” and the “old” page, respectively, eq_variance is the boolean signifying the outcome of the Levene’s test of equality of variances (i.e. the test of homogeneity of variance), ttest stands for the t statistic (the t-test outcome), ttest_p is the Type I Error probability (i.e. alpha), ttest_df stands for the respective number of degrees of freedom, ttest_method would have a value of Independent for equal variances and Welch when the homogenity of variance assumption is violated, eff_size is the measure of effect size (N.B. Cohen’s d for Independent and Glass’ delta for Welch), eff_magnitude is the categorization of the effect size (N.B. not available for Glass’ delta since it is uncertain whether the same categories apply as in the case of Cohen’s d), test_eq is the result of the TOST equivalence test.

campaigns <- unique(donationsData$campaign)
methodAResults <- data.frame(
  campaign = character(length(campaigns)),
  n_old = numeric(length(campaigns)),
  n_new = numeric(length(campaigns)),
  eq_variance = logical(length(campaigns)),
  ttest = numeric(length(campaigns)),
  ttest_p = numeric(length(campaigns)),
  ttest_df = numeric(length(campaigns)),
  ttest_method = character(length(campaigns)),
  eff_size = numeric(length(campaigns)),
  eff_mag = character(length(campaigns)),
  test_eq = logical(length(campaigns)),
  stringsAsFactors = F
)
for (i in 1:length(campaigns)) {
  # - select testData
  testData <- donationsData %>% 
    filter(campaign %in% campaigns[i]) %>% 
    select(expFactor, amountYearly)
  testData$expFactor <- factor(testData$expFactor, levels = c('new', 'old'))
  # - test for the homogeneity of variance
  testLevene <- levene.test(testData$amountYearly, group = testData$expFactor)
  if (testLevene$p.value > .05) {
    # - independent t-test
    ttest <- t.test(testData$amountYearly ~ testData$expFactor, 
                    var.equal = T)
    # - effect size
    cohend <- cohen.d(d = testData$amountYearly, f = testData$expFactor, 
                      pooled = T)
    # - TOST
    tost <- TOSTtwo(m1 = mean(testData$amountYearly[testData$expFactor == 'new']),
                    m2 = mean(testData$amountYearly[testData$expFactor == 'old']),
                    sd1 = sd(testData$amountYearly[testData$expFactor == 'new']),
                    sd2 = sd(testData$amountYearly[testData$expFactor == 'old']),
                    n1 = sum(testData$expFactor == 'new'),
                    n2 =  sum(testData$expFactor == 'old'),
                    low_eqbound_d = -.3,
                    high_eqbound_d = .3,
                    alpha = .05,
                    var.equal = T,
                    plot = F)
    # - store results
    methodAResults$campaign[i] <- campaigns[i]
    methodAResults$n_old[i] <- sum(testData$expFactor == 'old')
    methodAResults$n_new[i] <- sum(testData$expFactor == 'new')
    methodAResults$eq_variance[i] <- T
    methodAResults$ttest[i] <- ttest$statistic
    methodAResults$ttest_p[i] <- ttest$p.value
    methodAResults$ttest_df[i] <- ttest$parameter
    methodAResults$ttest_method[i] <- 'Independent'
    methodAResults$eff_size[i] <- cohend$estimate
    methodAResults$eff_mag[i] <- as.character(cohend$magnitude)
    methodAResults$test_eq[i] <- (tost$TOST_p1 < .05 & tost$TOST_p2 < .05)
  } else {
    # - perform correct t-test for unequal variances
    ttest <- t.test(testData$amountYearly ~ testData$expFactor, 
                    var.equal = F)
    # - effect size
    glassdelta <- (mean(testData$amountYearly[testData$expFactor == 'new']) - 
      mean(testData$amountYearly[testData$expFactor == 'old'])) /
      sd(testData$amountYearly[testData$expFactor == 'old'])
    # - TOST
    tost <- TOSTtwo(m1 = mean(testData$amountYearly[testData$expFactor == 'new']),
                    m2 = mean(testData$amountYearly[testData$expFactor == 'old']),
                    sd1 = sd(testData$amountYearly[testData$expFactor == 'new']),
                    sd2 = sd(testData$amountYearly[testData$expFactor == 'old']),
                    n1 = length(testData$amountYearly[testData$expFactor == 'new']),
                    n2 =  length(testData$amountYearly[testData$expFactor == 'old']),
                    low_eqbound_d = -.3,
                    high_eqbound_d = .3,
                    alpha = .05,
                    var.equal = F,
                    plot = F)
    # - store results
    methodAResults$campaign[i] <- campaigns[i]
    methodAResults$n_old[i] <- sum(testData$expFactor == 'old')
    methodAResults$n_new[i] <- sum(testData$expFactor == 'new')
    methodAResults$eq_variance[i] <- F
    methodAResults$ttest[i] <- ttest$statistic
    methodAResults$ttest_p[i] <- ttest$p.value
    methodAResults$ttest_df[i] <- ttest$parameter
    methodAResults$ttest_method[i] <- 'Welch'
    methodAResults$eff_size[i] <- glassdelta
    methodAResults$eff_mag[i] <- '-'
    methodAResults$test_eq[i] <- (tost$TOST_p1 < .05 & tost$TOST_p2 < .05)
  }
}
knitr::kable(methodAResults, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
campaign n_old n_new eq_variance ttest ttest_p ttest_df ttest_method eff_size eff_mag test_eq
mob05-ba-171218 2372 2431 TRUE 0.8583697 0.3907312 4801 Independent 0.0247731 negligible TRUE
35-ba-171218 2732 2482 TRUE -0.5540745 0.5795516 5212 Independent 0.0154845 negligible TRUE
pad04-ba-171218 2014 1940 TRUE 0.3133339 0.7540436 3952 Independent -0.0098875 negligible TRUE
en03-ba-171218 1621 1545 TRUE 0.6601157 0.5092277 3164 Independent 0.0234704 negligible TRUE
wpde-04-171218 2751 2767 TRUE -0.1485633 0.8819036 5516 Independent 0.0039980 negligible TRUE
38-ba-171223 3895 3960 TRUE 0.5589331 0.5762233 7853 Independent 0.0126134 negligible TRUE

Method B. Force Welch t-tests

Force Welch t-tests, assuming that is the recommended procedures when group sample sizes are not equal; this implies a correction for unequal variances.

Results. The columns have the same meaning as in the previous table, except for that the measure of effects size is always Glass’ delta.

campaigns <- unique(donationsData$campaign)
methodBResults <- data.frame(
  campaign = character(length(campaigns)),
  n_old = numeric(length(campaigns)),
  n_new = numeric(length(campaigns)),
  eq_variance = logical(length(campaigns)),
  ttest = numeric(length(campaigns)),
  ttest_p = numeric(length(campaigns)),
  ttest_df = numeric(length(campaigns)),
  ttest_method = character(length(campaigns)),
  eff_size = numeric(length(campaigns)),
  eff_mag = character(length(campaigns)),
  test_eq = logical(length(campaigns)),
  stringsAsFactors = F
)
for (i in 1:length(campaigns)) {
  # - select testData
  testData <- donationsData %>% 
    filter(campaign %in% campaigns[i]) %>% 
    select(expFactor, amountYearly)
  testData$expFactor <- factor(testData$expFactor, levels = c('new', 'old'))
  # - perform correct t-test for unequal variances
  ttest <- t.test(testData$amountYearly ~ testData$expFactor, 
                  var.equal = F)
  # - effect size
  glassdelta <- (mean(testData$amountYearly[testData$expFactor == 'new']) - 
                   mean(testData$amountYearly[testData$expFactor == 'old'])) /
    sd(testData$amountYearly[testData$expFactor == 'old'])
  # - TOST
  tost <- TOSTtwo(m1 = mean(testData$amountYearly[testData$expFactor == 'new']),
                  m2 = mean(testData$amountYearly[testData$expFactor == 'old']),
                  sd1 = sd(testData$amountYearly[testData$expFactor == 'new']),
                  sd2 = sd(testData$amountYearly[testData$expFactor == 'old']),
                  n1 = length(testData$amountYearly[testData$expFactor == 'new']),
                  n2 =  length(testData$amountYearly[testData$expFactor == 'old']),
                  low_eqbound_d = -.3,
                  high_eqbound_d = .3,
                  alpha = .05,
                  var.equal = F,
                  plot = F)
  # - store results
  methodBResults$campaign[i] <- campaigns[i]
  methodBResults$n_old[i] <- sum(testData$expFactor == 'old')
  methodBResults$n_new[i] <- sum(testData$expFactor == 'new')
  methodBResults$eq_variance[i] <- F
  methodBResults$ttest[i] <- ttest$statistic
  methodBResults$ttest_p[i] <- ttest$p.value
  methodBResults$ttest_df[i] <- ttest$parameter
  methodBResults$ttest_method[i] <- 'Welch'
  methodBResults$eff_size[i] <- glassdelta
  methodBResults$eff_mag[i] <- '-'
  methodBResults$test_eq[i] <- (tost$TOST_p1 < .05 & tost$TOST_p2 < .05)
}
knitr::kable(methodBResults, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
campaign n_old n_new eq_variance ttest ttest_p ttest_df ttest_method eff_size eff_mag test_eq
mob05-ba-171218 2372 2431 FALSE 0.8591243 0.3903149 4790.416 Welch 0.0257209 - TRUE
35-ba-171218 2732 2482 FALSE -0.5584103 0.5765883 5188.292 Welch -0.0143053 - TRUE
pad04-ba-171218 2014 1940 FALSE 0.3108147 0.7559614 3249.093 Welch 0.0131630 - TRUE
en03-ba-171218 1621 1545 FALSE 0.6572774 0.5110529 3009.390 Welch 0.0258540 - TRUE
wpde-04-171218 2751 2767 FALSE -0.1484904 0.8819613 5352.054 Welch -0.0036981 - TRUE
38-ba-171223 3895 3960 FALSE 0.5595943 0.5757724 7729.371 Welch 0.0136321 - TRUE

Method C. Fit a power law to each data set to learn about the nature of the sampling distribution

Fit a power law to each data set to learn about the nature of the sampling distribution; if the power law behavior cannot be excluded we conclude that the sampling distribution is not normal and choose to proceed with non-parametric tests; if the power law can be excluded, we choose to remove outliers and repeat the t-tests.

Test the power law behavior of the yearly amount distributions per campaign and per experimental group. CITATION. Colin S. Gillespie (2015). Fitting Heavy Tailed Distributions: The poweRlaw Package. Journal of Statistical Software, 64(2), 1-16. URL http://www.jstatsoft.org/v64/i02/.

NOTE. Do not run this; it is parallelized and takes a while of time to complete.

campaigns <- unique(donationsData$campaign)
# - test power laws
plList <- vector(mode = "list", length = length(campaigns))
for (i in 1:length(campaigns)) {
  testSetOld <- as.integer(donationsData$amountYearly[which(donationsData$campaign == campaigns[i] & donationsData$expFactor == 'old')])
  testSetNew <- as.integer(donationsData$amountYearly[which(donationsData$campaign == campaigns[i] & donationsData$expFactor == 'new')])
  mmTestSetOld <- displ$new(testSetOld)
  mmTestSetNew <- displ$new(testSetNew)
  plList[[i]]$old = bootstrap_p(mmTestSetOld, no_of_sims = 1000, threads = 8)
  plList[[i]]$new = bootstrap_p(mmTestSetNew, no_of_sims = 1000, threads = 8)
}
oldPValues <- sapply(plList, function(x) {x$old$p})
newPValues <- sapply(plList, function(x) {x$new$p})

Results. With 3 out of 12 data sets exhibiting power law behavior w. x_min set to the lowest value in the data set, we choose to remove the outliers and repeat the t-tests. NOTE. Re-running this with a previous estimation of x_min wouldn’t hurt.

Method C1. Run independent t-tests or Welch following the removal of outliers

Run independent t-tests or Welch, after testing for homogenity of variances w. Levene’s test, w. correction for unequal variances if necessary, and following the removal of outliers.

NOTE. Only extreme outliers on the upper tail of the distribution (> 3 * IQR, IQR == interquartile range) were removed. In effect, extremely high yearly donation amounts were removed.

campaigns <- unique(donationsData$campaign)
methodC1Results <- data.frame(
  campaign = character(length(campaigns)),
  n_old = numeric(length(campaigns)),
  n_new = numeric(length(campaigns)),
  eq_variance = logical(length(campaigns)),
  ttest = numeric(length(campaigns)),
  ttest_p = numeric(length(campaigns)),
  ttest_df = numeric(length(campaigns)),
  ttest_method = character(length(campaigns)),
  eff_size = numeric(length(campaigns)),
  eff_mag = character(length(campaigns)),
  test_eq = logical(length(campaigns)),
  stringsAsFactors = F
)
for (i in 1:length(campaigns)) {
  # - select testData
  testData <- donationsData %>% 
    filter(campaign %in% campaigns[i]) %>% 
    select(expFactor, amountYearly)
  testData$expFactor <- factor(testData$expFactor, levels = c('new', 'old'))
  # - remove outliers: one-sided, > 3*IQR per group
  testData$outlier <- logical(dim(testData)[1])
  wNewOut <- which(testData$amountYearly[testData$expFactor == 'new'] >
                     3 * IQR(testData$amountYearly[testData$expFactor == 'new']))
  testData$outlier[testData$expFactor == 'new'][wNewOut] <- T
  wOldOut <- which(testData$amountYearly[testData$expFactor == 'old'] >
                     3 * IQR(testData$amountYearly[testData$expFactor == 'old']))
  testData$outlier[testData$expFactor == 'old'][wOldOut] <- T
  testData <- filter(testData, outlier == F)
  # - test for the homogeneity of variance
  testLevene <- levene.test(testData$amountYearly, group = testData$expFactor)
  if (testLevene$p.value > .05) {
    # - independent t-test
    ttest <- t.test(testData$amountYearly ~ testData$expFactor, 
                    var.equal = T)
    # - effect size
    cohend <- cohen.d(d = testData$amountYearly, f = testData$expFactor, 
                      pooled = T)
    # - TOST
    tost <- TOSTtwo(m1 = mean(testData$amountYearly[testData$expFactor == 'new']),
                    m2 = mean(testData$amountYearly[testData$expFactor == 'old']),
                    sd1 = sd(testData$amountYearly[testData$expFactor == 'new']),
                    sd2 = sd(testData$amountYearly[testData$expFactor == 'old']),
                    n1 = sum(testData$expFactor == 'new'),
                    n2 =  sum(testData$expFactor == 'old'),
                    low_eqbound_d = -.3,
                    high_eqbound_d = .3,
                    alpha = .05,
                    var.equal = T,
                    plot = F)
    # - store results
    methodC1Results$campaign[i] <- campaigns[i]
    methodC1Results$n_old[i] <- sum(testData$expFactor == 'old')
    methodC1Results$n_new[i] <- sum(testData$expFactor == 'new')
    methodC1Results$eq_variance[i] <- T
    methodC1Results$ttest[i] <- ttest$statistic
    methodC1Results$ttest_p[i] <- ttest$p.value
    methodC1Results$ttest_df[i] <- ttest$parameter
    methodC1Results$ttest_method[i] <- 'Independent'
    methodC1Results$eff_size[i] <- cohend$estimate
    methodC1Results$eff_mag[i] <- as.character(cohend$magnitude)
    methodC1Results$test_eq[i] <- (tost$TOST_p1 < .05 & tost$TOST_p2 < .05)
  } else {
    # - perform correct t-test for unequal variances
    ttest <- t.test(testData$amountYearly ~ testData$expFactor, 
                    var.equal = F)
    # - effect size
    glassdelta <- (mean(testData$amountYearly[testData$expFactor == 'new']) - 
                     mean(testData$amountYearly[testData$expFactor == 'old'])) /
      sd(testData$amountYearly[testData$expFactor == 'old'])
    # - TOST
    tost <- TOSTtwo(m1 = mean(testData$amountYearly[testData$expFactor == 'new']),
                    m2 = mean(testData$amountYearly[testData$expFactor == 'old']),
                    sd1 = sd(testData$amountYearly[testData$expFactor == 'new']),
                    sd2 = sd(testData$amountYearly[testData$expFactor == 'old']),
                    n1 = length(testData$amountYearly[testData$expFactor == 'new']),
                    n2 =  length(testData$amountYearly[testData$expFactor == 'old']),
                    low_eqbound_d = -.3,
                    high_eqbound_d = .3,
                    alpha = .05,
                    var.equal = F,
                    plot = F)
    # - store results
    methodC1Results$campaign[i] <- campaigns[i]
    methodC1Results$n_old[i] <- sum(testData$expFactor == 'old')
    methodC1Results$n_new[i] <- sum(testData$expFactor == 'new')
    methodC1Results$eq_variance[i] <- F
    methodC1Results$ttest[i] <- ttest$statistic
    methodC1Results$ttest_p[i] <- ttest$p.value
    methodC1Results$ttest_df[i] <- ttest$parameter
    methodC1Results$ttest_method[i] <- 'Welch'
    methodC1Results$eff_size[i] <- glassdelta
    methodC1Results$eff_mag[i] <- '-'
    methodC1Results$test_eq[i] <- (tost$TOST_p1 < .05 & tost$TOST_p2 < .05)
  }
}
knitr::kable(methodC1Results, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
campaign n_old n_new eq_variance ttest ttest_p ttest_df ttest_method eff_size eff_mag test_eq
mob05-ba-171218 2225 2258 TRUE -0.0984794 0.9215560 4481.000 Independent -0.0029417 negligible TRUE
35-ba-171218 2648 2404 TRUE -0.5889559 0.5559172 5050.000 Independent 0.0165837 negligible TRUE
pad04-ba-171218 1944 1873 TRUE -1.0888217 0.2763013 3815.000 Independent 0.0352502 negligible TRUE
en03-ba-171218 1570 1493 TRUE -0.0928635 0.9260181 3061.000 Independent -0.0033569 negligible TRUE
wpde-04-171218 2151 2160 FALSE 0.0460051 0.9633083 4306.996 Welch 0.0013893 - TRUE
38-ba-171223 3781 3830 TRUE -0.2823690 0.7776683 7609.000 Independent -0.0064734 negligible TRUE

Method C2. Force Welch t-test following the removal of outliers

Force Welch t-test, after testing for homogenity of variances w. Levene’s test, w. correction for unequal variances if necessary, and following the removal of outliers.

NOTE. Only extreme outliers on the upper tail of the distribution (> 3 * IQR, IQR == interquartile range) were removed. In effect, extremely high yearly donation amounts were removed.

NOTE. The effect size (eff_size) is always Glass’ delta.

campaigns <- unique(donationsData$campaign)
methodC2Results <- data.frame(
  campaign = character(length(campaigns)),
  n_old = numeric(length(campaigns)),
  n_new = numeric(length(campaigns)),
  eq_variance = logical(length(campaigns)),
  ttest = numeric(length(campaigns)),
  ttest_p = numeric(length(campaigns)),
  ttest_df = numeric(length(campaigns)),
  ttest_method = character(length(campaigns)),
  eff_size = numeric(length(campaigns)),
  eff_mag = character(length(campaigns)),
  test_eq = logical(length(campaigns)),
  stringsAsFactors = F
)
for (i in 1:length(campaigns)) {
  # - select testData
  testData <- donationsData %>% 
    filter(campaign %in% campaigns[i]) %>% 
    select(expFactor, amountYearly)
  testData$expFactor <- factor(testData$expFactor, levels = c('new', 'old'))
  # - remove outliers: one-sided, > 3*IQR per group
  testData$outlier <- logical(dim(testData)[1])
  wNewOut <- which(testData$amountYearly[testData$expFactor == 'new'] >
                     3 * IQR(testData$amountYearly[testData$expFactor == 'new']))
  testData$outlier[testData$expFactor == 'new'][wNewOut] <- T
  wOldOut <- which(testData$amountYearly[testData$expFactor == 'old'] >
                     3 * IQR(testData$amountYearly[testData$expFactor == 'old']))
  testData$outlier[testData$expFactor == 'old'][wOldOut] <- T
  testData <- filter(testData, outlier == F)
  # - perform correct t-test for unequal variances
  ttest <- t.test(testData$amountYearly ~ testData$expFactor, 
                  var.equal = F)
  # - effect size
  glassdelta <- (mean(testData$amountYearly[testData$expFactor == 'new']) - 
                   mean(testData$amountYearly[testData$expFactor == 'old'])) /
    sd(testData$amountYearly[testData$expFactor == 'old'])
  # - TOST
  tost <- TOSTtwo(m1 = mean(testData$amountYearly[testData$expFactor == 'new']),
                  m2 = mean(testData$amountYearly[testData$expFactor == 'old']),
                  sd1 = sd(testData$amountYearly[testData$expFactor == 'new']),
                  sd2 = sd(testData$amountYearly[testData$expFactor == 'old']),
                  n1 = length(testData$amountYearly[testData$expFactor == 'new']),
                  n2 =  length(testData$amountYearly[testData$expFactor == 'old']),
                  low_eqbound_d = -.3,
                  high_eqbound_d = .3,
                  alpha = .05,
                  var.equal = F,
                  plot = F)
  # - store results
  methodC2Results$campaign[i] <- campaigns[i]
  methodC2Results$n_old[i] <- sum(testData$expFactor == 'old')
  methodC2Results$n_new[i] <- sum(testData$expFactor == 'new')
  methodC2Results$eq_variance[i] <- F
  methodC2Results$ttest[i] <- ttest$statistic
  methodC2Results$ttest_p[i] <- ttest$p.value
  methodC2Results$ttest_df[i] <- ttest$parameter
  methodC2Results$ttest_method[i] <- 'Welch'
  methodC2Results$eff_size[i] <- glassdelta
  methodC2Results$eff_mag[i] <- '-'
  methodC2Results$test_eq[i] <- (tost$TOST_p1 < .05 & tost$TOST_p2 < .05)
}
knitr::kable(methodC2Results, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
campaign n_old n_new eq_variance ttest ttest_p ttest_df ttest_method eff_size eff_mag test_eq
mob05-ba-171218 2225 2258 FALSE -0.0984699 0.9215636 4477.542 Welch -0.0029226 - TRUE
35-ba-171218 2648 2404 FALSE -0.5886759 0.5561053 4993.308 Welch -0.0166699 - TRUE
pad04-ba-171218 1944 1873 FALSE -1.0887222 0.2763453 3808.242 Welch -0.0353386 - TRUE
en03-ba-171218 1570 1493 FALSE -0.0927810 0.9260837 3038.731 Welch -0.0034163 - TRUE
wpde-04-171218 2151 2160 FALSE 0.0460051 0.9633083 4306.996 Welch 0.0013893 - TRUE
38-ba-171223 3781 3830 FALSE -0.2823820 0.7776583 7608.753 Welch -0.0064970 - TRUE

Method D1. Non-parametric tests: Mann-Whitney U Test, no removal of outliers

Conduct non-parametric, independent 2-group Mann-Whitney U Test; no removal of outliers.

Results. campaign stands for the campaign code, n_old and n_new are the number of observations for the “new” and the “old” page respectively, W is the Wilcoxon test statistics, and MWUTest_p the associated probability of the Type I Error.

campaigns <- unique(donationsData$campaign)
methodD1Results <- data.frame(
  campaign = character(length(campaigns)),
  n_old = numeric(length(campaigns)),
  n_new = numeric(length(campaigns)),
  W = numeric(length(campaigns)),
  MWUTest_p = numeric(length(campaigns)),
  stringsAsFactors = F
)
for (i in 1:length(campaigns)) {
  # - select testData
  testData <- donationsData %>% 
    filter(campaign %in% campaigns[i]) %>% 
    select(expFactor, amountYearly)
  testData$expFactor <- factor(testData$expFactor, levels = c('new', 'old'))
  # - non-parametric test
  mwu <- wilcox.test(testData$amountYearly ~ testData$expFactor) 
  # - store results
  methodD1Results$campaign[i] <- campaigns[i]
  methodD1Results$n_old[i] <- sum(testData$expFactor == 'old')
  methodD1Results$n_new[i] <- sum(testData$expFactor == 'new')
  methodD1Results$W[i] <- mwu$statistic
  methodD1Results$MWUTest_p[i] <- mwu$p.value
}
knitr::kable(methodD1Results, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
campaign n_old n_new W MWUTest_p
mob05-ba-171218 2372 2431 2913410 0.4998617
35-ba-171218 2732 2482 3340974 0.3442295
pad04-ba-171218 2014 1940 1904823 0.1598756
en03-ba-171218 1621 1545 1236390 0.5227544
wpde-04-171218 2751 2767 3811216 0.9287713
38-ba-171223 3895 3960 7687976 0.8034789

Method D2. Non-parametric tests: Mann-Whitney U Test, following the removal of outliers

Conduct non-parametric, independent 2-group Mann-Whitney U Test, following the removal of outliers.

NOTE. Only extreme outliers on the upper tail of the distribution (> 3 * IQR, IQR == interquartile range) were removed. In effect, extremely high yearly donation amounts were removed.

campaigns <- unique(donationsData$campaign)
methodD2Results <- data.frame(
  campaign = character(length(campaigns)),
  n_old = numeric(length(campaigns)),
  n_new = numeric(length(campaigns)),
  W = numeric(length(campaigns)),
  MWUTest_p = numeric(length(campaigns)),
  stringsAsFactors = F
)
for (i in 1:length(campaigns)) {
  # - select testData
  testData <- donationsData %>% 
    filter(campaign %in% campaigns[i]) %>% 
    select(expFactor, amountYearly)
  testData$expFactor <- factor(testData$expFactor, levels = c('new', 'old'))
  # - remove outliers: one-sided, > 3*IQR per group
  testData$outlier <- logical(dim(testData)[1])
  wNewOut <- which(testData$amountYearly[testData$expFactor == 'new'] >
                     3 * IQR(testData$amountYearly[testData$expFactor == 'new']))
  testData$outlier[testData$expFactor == 'new'][wNewOut] <- T
  wOldOut <- which(testData$amountYearly[testData$expFactor == 'old'] >
                     3 * IQR(testData$amountYearly[testData$expFactor == 'old']))
  testData$outlier[testData$expFactor == 'old'][wOldOut] <- T
  testData <- filter(testData, outlier == F)
  # - non-parametric test
  mwu <- wilcox.test(testData$amountYearly ~ testData$expFactor) 
  # - store results
  methodD2Results$campaign[i] <- campaigns[i]
  methodD2Results$n_old[i] <- sum(testData$expFactor == 'old')
  methodD2Results$n_new[i] <- sum(testData$expFactor == 'new')
  methodD2Results$W[i] <- mwu$statistic
  methodD2Results$MWUTest_p[i] <- mwu$p.value
}
knitr::kable(methodD2Results, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
campaign n_old n_new W MWUTest_p
mob05-ba-171218 2225 2258 2516217 0.9159759
35-ba-171218 2648 2404 3131223 0.2980615
pad04-ba-171218 1944 1873 1772213 0.1402104
en03-ba-171218 1570 1493 1153248 0.4243507
wpde-04-171218 2151 2160 2326203 0.9372047
38-ba-171223 3781 3830 7188939 0.5748126

1.6 A/B testing (per campaign): Opt in

Outline

Binary logistic regression was a method of our choice, with the experimental A/B factor (“new”/“old”) as a predictor of opt_in. The “old” level of the experimental factor was used as a baseline. Conclusion. Except for the mob05-ba-171218 campaign where the experimental factor did not have a statistically significant effect upon opt_in, the new page seems to always induce a reduction in the probability to opt_in.

Binary Logistic Regression: A/B vs. Opt in

Results. campaign stands for the campaign code, n_old and n_new the number of observations for the “new” and the “old” page, respectively, coeff is the regression coefficient for the A/B experimental factor (NOTE: using old as a baseline), exp_coeff is the exponential of the regression coefficient - the delta odds, should be interpreted as the increase in the odds of opting in vs. not opting in with a unit increase in the experimental factors which in our case means switching from the old to the new page, coeff_z is the value of the Wald’s test (similar to t-test in linear regression, tests if the regression coefficient is significantly different from zero), coeff_p the respective probability of the Type I Error, sig whether the Wald’s test have reached the conventional alpha < .05 significance criterion.

campaigns <- unique(donationsData$campaign)
opt_in_BLR_Results <- data.frame(
  campaign = character(length(campaigns)),
  n_old = numeric(length(campaigns)),
  n_new = numeric(length(campaigns)),
  coeff = numeric(length(campaigns)),
  exp_coeff = numeric(length(campaigns)),
  coeff_z = numeric(length(campaigns)),
  coeff_p = numeric(length(campaigns)),
  sig = logical(length(campaigns)),
  stringsAsFactors = F
)
for (i in 1:length(campaigns)) {
  # - select testData
  testData <- donationsData %>% 
    filter(campaign %in% campaigns[i]) %>% 
    select(expFactor, amountYearly, opt_in)
  testData$expFactor <- factor(testData$expFactor, levels = c('old', 'new'))
  # - Binary logistic regression
  tmodel <- glm(testData$opt_in ~ testData$expFactor,
                family = "binomial") 
  # - store results
  opt_in_BLR_Results$campaign[i] <- campaigns[i]
  opt_in_BLR_Results$n_old[i] <- sum(testData$expFactor == 'old')
  opt_in_BLR_Results$n_new[i] <- sum(testData$expFactor == 'new')
  opt_in_BLR_Results$coeff[i] <- summary(tmodel)$coefficients[2, 1]
  opt_in_BLR_Results$exp_coeff[i] <- exp(summary(tmodel)$coefficients[2, 1])
  opt_in_BLR_Results$coeff_z[i] <- summary(tmodel)$coefficients[2, 3]
  opt_in_BLR_Results$coeff_p[i] <- summary(tmodel)$coefficients[2, 4]
  opt_in_BLR_Results$sig[i] <- summary(tmodel)$coefficients[2, 4] < .05
}
knitr::kable(opt_in_BLR_Results, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
campaign n_old n_new coeff exp_coeff coeff_z coeff_p sig
mob05-ba-171218 2372 2431 -0.0589191 0.9427831 -0.9386565 0.3479071 FALSE
35-ba-171218 2732 2482 -0.6335634 0.5306973 -9.4906179 0.0000000 TRUE
pad04-ba-171218 2014 1940 -0.8305340 0.4358165 -11.0079858 0.0000000 TRUE
en03-ba-171218 1621 1545 -0.5292162 0.5890665 -6.3989787 0.0000000 TRUE
wpde-04-171218 2751 2767 -0.4554023 0.6341928 -7.4850421 0.0000000 TRUE
38-ba-171223 3895 3960 -0.6537384 0.5200978 -12.2507708 0.0000000 TRUE

1. Memberhsip Data Set

1.1 Clean up

  • Experimental factor: in the membershipData$keyword, anything ending in “-ctrl” or “var”
  • In membershipData$status: keep only “1” cases, namely: “The only important takeaway here is that only memberships with status 1 are booked and completed memberships.” (see Reference doc)
membershipData <- filter(membershipData, grepl("-ctrl$|var$", membershipData$banner_keyword))
membershipData <- filter(membershipData, status == 1)

1. 2 Recode the experimental factor membershipData$keyword

  • See: https://phabricator.wikimedia.org/T194744#4207413
  • NOTE: In respect to the following comment:
  • “A special case is the last campaign on wikipedia.de (”wpde“). It featured four banners and has therefore four keywords.Every user in the campaign saw one large banner (”fulltop“”) and after that only small banners (“top”“). So one can aggregate the data of the large and small banners or treat it as two test campaigns.”
  • The four paired banners for this campaign were treated as two banners only (old/new);
  • ratio: every user necessarily saw a pair of banners; makes no sense treating them as different factors.
recodingScheme <- read.csv('data/recodingScheme.csv', 
                           header = T,
                           stringsAsFactors = F,
                           check.names = F)
oldCodes <- unique(recodingScheme$`keyword of old lp`)
newCodes <- unique(recodingScheme$`keyword of new lp`)
membershipData$expFactor <- sapply(membershipData$banner_keyword, function(x) {
  if (x %in% oldCodes) {
    return("old")
  } else if (x %in% newCodes) {
    return("new")
  } else {
    return("Unnasigned")
  }
})
# - filter out 'Unnasigned' codes:
membershipData <- filter(membershipData, !(expFactor == 'Unnasigned'))

1. 3 Rescale membership_fee to yearly data by interval

  • The yearly fee can be paid once a year or every X month; the number in the data field signifies 1 = booking every month , 3 = booking every three months, 6 = booking every six months, 12 = booking every twelve months
  • NOTE: “0” value for the donationsData$interval would mean: once per year
membershipData$amountYearly <- numeric(dim(membershipData)[1])
membershipData$amountYearly[membershipData$membership_fee_interval == 0 | membershipData$membership_fee_interval == 12] <- 
  membershipData$membership_fee[membershipData$membership_fee_interval == 0 | membershipData$membership_fee_interval == 12]
membershipData$amountYearly[membershipData$membership_fee_interval == 1] <- 
  membershipData$membership_fee[membershipData$membership_fee_interval == 1] * 12
membershipData$amountYearly[membershipData$membership_fee_interval == 3] <- 
  membershipData$membership_fee[membershipData$membership_fee_interval == 3] * 4
membershipData$amountYearly[membershipData$membership_fee_interval == 6] <- 
  membershipData$membership_fee[membershipData$membership_fee_interval == 6] * 2

1. 4 Select only relevant variables

membershipData <- select(membershipData,
                         banner_campaign, expFactor, amountYearly, donation_receipt)

1. 5 A/B tests: membership_fee

Outline

The analysis of the Membership data set relies on non-parametric tests only (Mann-Whitney U test), because we get to have really small sample sizes per campaign following the data clean-up. Conclusion. No effect of the new page in any of the campaigns.

Method D1. Non-parametric tests

Results. campaign stands for the campaign code, n_old and n_new are the number of observations for the “new” and the “old” page respectively, W is the Wilcoxon test statistics, and MWUTest_p the associated probability of the Type I Error.

campaigns <- unique(membershipData$banner_campaign)
methodD1Results_memberhsip <- data.frame(
  campaign = character(length(campaigns)),
  n_old = numeric(length(campaigns)),
  n_new = numeric(length(campaigns)),
  W = numeric(length(campaigns)),
  MWUTest_p = numeric(length(campaigns)),
  stringsAsFactors = F
)
for (i in 1:length(campaigns)) {
  # - select testData
  testData <- membershipData %>% 
    filter(banner_campaign %in% campaigns[i]) %>% 
    select(expFactor, amountYearly)
  testData$expFactor <- factor(testData$expFactor, levels = c('new', 'old'))
  # - non-parametric test
  mwu <- wilcox.test(testData$amountYearly ~ testData$expFactor) 
  # - store results
  methodD1Results_memberhsip$campaign[i] <- campaigns[i]
  methodD1Results_memberhsip$n_old[i] <- sum(testData$expFactor == 'old')
  methodD1Results_memberhsip$n_new[i] <- sum(testData$expFactor == 'new')
  methodD1Results_memberhsip$W[i] <- mwu$statistic
  methodD1Results_memberhsip$MWUTest_p[i] <- mwu$p.value
}
knitr::kable(methodD1Results_memberhsip, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
campaign n_old n_new W MWUTest_p
35-ba-171218 15 7 58.5 0.6714894
wpde-04-171218 35 25 365.0 0.2720977
mob05-ba-171218 17 6 40.0 0.4116710
pad04-ba-171218 10 5 23.0 0.8136099
en03-ba-171218 11 8 42.0 0.8950513
38-ba-171223 28 23 315.0 0.8952855
LS0tCnRpdGxlOiAiRnVuZHJhaXNpbmc6IERvbmF0aW9ucyBhbmQgTWVtYmVyc2hpcCBEYXRhIFNldHMgQW5hbHlzaXMiCmF1dGhvcjogIkdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERSIKZGF0ZTogIkp1bmUgMTcsIDIwMTgiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogc2ltcGxleAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgdG9jX2Zsb2F0OiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAotLS0KCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZWAuIAoKIyMgMC4gU2V0dXAKCkxpYnJhcmllcyBzZXR1cC4KCmBgYHtyLCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGLCByZXN1bHRzID0gJ2hpZGUnfQojICFkaWFnbm9zdGljcyBvZmYKIyMjIC0tLSBTZXR1cAprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSA4KSAKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkobGF3c3RhdCkKbGlicmFyeShlZmZzaXplKQpsaWJyYXJ5KFRPU1RFUikKbGlicmFyeShwb3dlUmxhdykKbGlicmFyeShrYWJsZUV4dHJhKQpgYGAKCiMjIyAwLjEgRGF0YSBzZXRzCgpMb2FkIGRhdGEgc2V0cy4KCmBgYHtyLCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGLCByZXN1bHRzID0gJ2hpZGUnfQpkb25hdGlvbnNEYXRhIDwtIGZyZWFkKCdkYXRhL2NhbXBhaWduLTIwMTctZG9uYXRpb25zLmNzdicsIAogICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsIGRhdGEudGFibGUgPSBGKQptZW1iZXJzaGlwRGF0YSA8LSBmcmVhZCgnZGF0YS9jYW1wYWlnbi0yMDE3LW1lbWJlcnNoaXBzLmNzdicsIAogICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCBkYXRhLnRhYmxlID0gRikKYGBgCgojIyMgMC4xIEdlbmVyYWwgY29uc2lkZXJhdGlvbnMKCipUaGUgZG9uYXRpb25zIGRhdGFzZXQqCgotIFJlZmVyIHRvOiBodHRwczovL3BoYWJyaWNhdG9yLndpa2ltZWRpYS5vcmcvVDE5NDc0NAotIEdvYWw6IHRoZXJlIGFyZSB0d28gcGFnZXM6IHRoZSAib2xkIiBvbmUsIHRoZSAibmV3IiBvbmU7IEEvQiB0ZXN0ICJvbGQiIHZzLiAibmV3IgotIERlcGVuZGVudCB2YXJpYWJsZTogZG9uYXRpb25zRGF0YSRhbW91bnQKLSBGYWN0b3I6IGluIHRoZSBkb25hdGlvbnNEYXRhJGtleXdvcmQsIGFueXRoaW5nIGVuZGluZyBpbiAiLWN0cmwiIG9yICJ2YXIiCi0gVGhlIChyZSljb2Rpbmcgc2NoZW1hIGZvciB0aGUgZG9uYXRpb25zRGF0YSRrZXl3b3JkIGZhY3RvciBpczogaHR0cHM6Ly9waGFicmljYXRvci53aWtpbWVkaWEub3JnL1QxOTQ3NDQjNDIwNzQxMwotIFJlc2NhbGUgdG8geWVhcmx5IGRhdGEgYnkgZG9uYXRpb25zRGF0YSRpbnRlcnZhbCAoc2VlIHRoZSBNZW1iZXJzaGlwIGRhdGFzZXQpOwotIE5PVEU6ICIwIiB2YWx1ZSBmb3IgdGhlIGRvbmF0aW9uc0RhdGEkaW50ZXJ2YWwgd291bGQgbWVhbjogb25jZSBwZXIgeWVhci4KLSBBbm90aGVyIGRlcGVuZGVudCB2YXJpYWJsZSB0byBsb29rIGF0OiBkb25hdGlvbnNEYXRhJG9wdF9pbi4gCi0gREFUQSBDTEVBTklORzogdGhlIGRvbmF0aW9uc0RhdGEkc3RhdHVzOgotIENvZGVzOiBCLCBOLCBYLCBaIAotIFggPT0gdW5jb21wbGV0ZWQsIGZpbHRlciB0aGlzIG91dAotIEIgPT0gYm9va2VkLCB3ZSBnb3QgdGhlIG1vbmV5Ci0gTiA9PSBib29rZWQsIHdlIGdvdCB0aGUgbW9uZXkKLSBaID0gcHJvbWlzZSB0aGF0IHRoZXkgd291bGQgc2VuZCB0aGUgbW9uZXksIGZpbHRlciB0aGlzIG91dAoKCipUaGUgbWVtYmVyc2hpcCBkYXRhc2V0KgoKLSBUaGUgZGVwZW5kZW50IHZhcmlhYmxlIGlzOiBtZW1iZXJzaGlwX2ZlZWUKLSBNZW1iZXJzaGlwX2ZlZV9pbnRlcnZhbDogdGhlIHllYXJseSBmZWUgY2FuIGJlIHBhaWQgb25jZSBhIHllYXIgb3IgZXZlcnkgWCBtb250aDsgdGhlIG51bWJlciBpbiB0aGUgZGF0YSBmaWVsZCBzaWduaWZpZXM6IDEJPSBib29raW5nIGV2ZXJ5IG1vbnRoLCAzCT0gYm9va2luZyBldmVyeSB0aHJlZSBtb250aHMsIDYJPSBib29raW5nIGV2ZXJ5IHNpeCBtb250aHMgMTIJPSBib29raW5nIGV2ZXJ5IHR3ZWx2ZSBtb250aHM7IAotIHJlc2NhbGUgYWxsIHRoZSBtZW1iZXJzaGlwIGZlZSBvYnNlcnZhdGlvbnMgdG8gYSBvbmUteWVhciBwZXJpb2QuCgoKIyMgMS4gRG9uYXRpb25zIERhdGEgU2V0CgojIyMgMS4xIENsZWFuIHVwCgpFeHBlcmltZW50YWwgZmFjdG9yOiBpbiB0aGUgYGRvbmF0aW9uc0RhdGEka2V5d29yZGAsIGFueXRoaW5nIGVuZGluZyBpbiAiLWN0cmwiIG9yICJ2YXIiLgpBbHNvLCBpbiBpbiBgZG9uYXRpb25zRGF0YSRzdGF0dXNgOiBmaWx0ZXIgb3V0IFogYW5kIFggY2FzZXMuCgpgYGB7ciwgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpkb25hdGlvbnNEYXRhIDwtIGZpbHRlcihkb25hdGlvbnNEYXRhLCBncmVwbCgiLWN0cmwkfHZhciQiLCBkb25hdGlvbnNEYXRhJGtleXdvcmQpKQpkb25hdGlvbnNEYXRhIDwtIGZpbHRlcihkb25hdGlvbnNEYXRhLCAhKHN0YXR1cyAlaW4lIGMoJ1gnLCAnWicpKSkKYGBgCgojIyMgMS4yIFJlY29kZSB0aGUgZXhwZXJpbWVudGFsIGZhY3RvciBkb25hdGlvbnNEYXRhJGtleXdvcmQKCi0gU2VlOiBodHRwczovL3BoYWJyaWNhdG9yLndpa2ltZWRpYS5vcmcvVDE5NDc0NCM0MjA3NDEzCi0gTk9URTogSW4gcmVzcGVjdCB0byB0aGUgZm9sbG93aW5nIGNvbW1lbnQ6IAotICJBIHNwZWNpYWwgY2FzZSBpcyB0aGUgbGFzdCBjYW1wYWlnbiBvbiB3aWtpcGVkaWEuZGUgKCJ3cGRlIikuIAotIEl0IGZlYXR1cmVkIGZvdXIgYmFubmVycyBhbmQgaGFzIHRoZXJlZm9yZSBmb3VyIGtleXdvcmRzLiAKLSBFdmVyeSB1c2VyIGluIHRoZSBjYW1wYWlnbiBzYXcgb25lIGxhcmdlIGJhbm5lciAo4oCcZnVsbHRvcOKAnSkgYW5kIGFmdGVyIAotIHRoYXQgb25seSBzbWFsbCBiYW5uZXJzICjigJx0b3DigJ0pLiBTbyBvbmUgY2FuIGFnZ3JlZ2F0ZSB0aGUgZGF0YSBvZiAKLSB0aGUgbGFyZ2UgYW5kIHNtYWxsIGJhbm5lcnMgb3IgdHJlYXQgaXQgYXMgdHdvIHRlc3QgY2FtcGFpZ25zLiIKLSBUaGUgZm91ciBwYWlyZWQgYmFubmVycyBmb3IgdGhpcyBjYW1wYWlnbiB3ZXJlIHRyZWF0ZWQgYXMgdHdvIGJhbm5lcnMgb25seSAob2xkL25ldyk7Ci0gcmF0aW86IGV2ZXJ5IHVzZXIgbmVjZXNzYXJpbHkgc2F3IGEgcGFpciBvZiBiYW5uZXJzOyBtYWtlcyBubyBzZW5zZSB0cmVhdGluZyB0aGVtIGFzIGRpZmZlcmVudCBmYWN0b3JzLgoKYGBge3IsIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KcmVjb2RpbmdTY2hlbWUgPC0gcmVhZC5jc3YoJ2RhdGEvcmVjb2RpbmdTY2hlbWUuY3N2JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYpCm9sZENvZGVzIDwtIHVuaXF1ZShyZWNvZGluZ1NjaGVtZSRga2V5d29yZCBvZiBvbGQgbHBgKQpuZXdDb2RlcyA8LSB1bmlxdWUocmVjb2RpbmdTY2hlbWUkYGtleXdvcmQgb2YgbmV3IGxwYCkKZG9uYXRpb25zRGF0YSRleHBGYWN0b3IgPC0gc2FwcGx5KGRvbmF0aW9uc0RhdGEka2V5d29yZCwgZnVuY3Rpb24oeCkgewogIGlmICh4ICVpbiUgb2xkQ29kZXMpIHsKICAgIHJldHVybigib2xkIikKICB9IGVsc2UgaWYgKHggJWluJSBuZXdDb2RlcykgewogICAgcmV0dXJuKCJuZXciKQogIH0gZWxzZSB7CiAgICByZXR1cm4oIlVubmFzaWduZWQiKQogIH0KfSkKIyAtIGZpbHRlciBvdXQgJ1VubmFzaWduZWQnIGNvZGVzOgpkb25hdGlvbnNEYXRhIDwtIGZpbHRlcihkb25hdGlvbnNEYXRhLCAhKGV4cEZhY3RvciA9PSAnVW5uYXNpZ25lZCcpKQpgYGAKCiMjIyAxLjMgUmVzY2FsZSBhbW91bnQgdG8geWVhcmx5IGRhdGEgYnkgaW50ZXJ2YWwKCi0gVGhlIHllYXJseSBmZWUgY2FuIGJlIHBhaWQgb25jZSBhIHllYXIgb3IgZXZlcnkgWCBtb250aDsgdGhlIG51bWJlciBpbiB0aGUgZGF0YSBmaWVsZCBzaWduaWZpZXMgWAotIDEJPSBib29raW5nIGV2ZXJ5IG1vbnRoIAotIDMJPSBib29raW5nIGV2ZXJ5IHRocmVlIG1vbnRocyAKLSA2CT0gYm9va2luZyBldmVyeSBzaXggbW9udGhzIAotIDEyCT0gYm9va2luZyBldmVyeSB0d2VsdmUgbW9udGhzCi0gTk9URTogIjAiIHZhbHVlIGZvciB0aGUgYGRvbmF0aW9uc0RhdGEkaW50ZXJ2YWxgIHdvdWxkIG1lYW46IG9uY2UgcGVyIHllYXIKCmBgYHtyLCBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmRvbmF0aW9uc0RhdGEkYW1vdW50WWVhcmx5IDwtIG51bWVyaWMoZGltKGRvbmF0aW9uc0RhdGEpWzFdKQpkb25hdGlvbnNEYXRhJGFtb3VudFllYXJseVtkb25hdGlvbnNEYXRhJGludGVydmFsID09IDAgfCBkb25hdGlvbnNEYXRhJGludGVydmFsID09IDEyXSA8LSAKICBkb25hdGlvbnNEYXRhJGFtb3VudFtkb25hdGlvbnNEYXRhJGludGVydmFsID09IDAgfCBkb25hdGlvbnNEYXRhJGludGVydmFsID09IDEyXQpkb25hdGlvbnNEYXRhJGFtb3VudFllYXJseVtkb25hdGlvbnNEYXRhJGludGVydmFsID09IDFdIDwtIAogIGRvbmF0aW9uc0RhdGEkYW1vdW50W2RvbmF0aW9uc0RhdGEkaW50ZXJ2YWwgPT0gMV0gKiAxMgpkb25hdGlvbnNEYXRhJGFtb3VudFllYXJseVtkb25hdGlvbnNEYXRhJGludGVydmFsID09IDNdIDwtIAogIGRvbmF0aW9uc0RhdGEkYW1vdW50W2RvbmF0aW9uc0RhdGEkaW50ZXJ2YWwgPT0gM10gKiA0CmRvbmF0aW9uc0RhdGEkYW1vdW50WWVhcmx5W2RvbmF0aW9uc0RhdGEkaW50ZXJ2YWwgPT0gNl0gPC0gCiAgZG9uYXRpb25zRGF0YSRhbW91bnRbZG9uYXRpb25zRGF0YSRpbnRlcnZhbCA9PSA2XSAqIDIKYGBgCgojIyMgMS40IFNlbGVjdCBvbmx5IHJlbGV2YW50IHZhcmlhYmxlcwoKYGBge3IsIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KZG9uYXRpb25zRGF0YSA8LSBzZWxlY3QoZG9uYXRpb25zRGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduLCBleHBGYWN0b3IsIGFtb3VudFllYXJseSwgb3B0X2luKQpgYGAKCiMjIyAxLjUgQS9CIHRlc3RpbmcgKHBlciBjYW1wYWlnbik6IEFtb3VudAoKIyMjIyBPdXRsaW5lCgpUaGUgYW5hbHl0aWNhbCB3b3JrZmxvdyBpcyBhcyBmb2xsb3dzOgoKLSBXZSBhbHdheXMgY29uZHVjdCBhIGNhbXBhaWduLXdpc2UgQS9CIHRlc3Q7IHNvLCBlYWNoIGRpZmZlcmVudCBtZXRob2QgcmVzdWx0cyBpbiBhcyBtYW55IGFuYWx5c2VzIGFzIHRoZXJlIHdlcmUgY2FtcGFpZ25zIGluIHRoZSBkYXRhIHNldC4KLSAqKk1ldGhvZCBBLioqIFdlIHJ1biB0LXRlc3RzIHByZWNlZGVkIGJ5IExldmVuZSdzIHRlc3QuIElmIHRoZSBMZXZlbmUncyB0ZXN0IGltcGxpZXMgdGhlIGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlLCB3ZSBydW4gaW5kZXBlbmRlbnQgdC10ZXN0cywgb3RoZXJ3aXNlIHdlIHJ1biBXZWxjaCB0LXRlc3RzLgotICoqTWV0aG9kIEIuKiogR2l2ZW4gdGhhdCB0aGUgc2FtcGxlIHNpemVzIGZvciB0aGUgZXhwZXJpbWVudGFsIEEvQiBmYWN0b3IgKCJuZXciLyJvbGQiKSBhcmUgYWx3YXlzIHVuZXF1YWwsIHdlIGZvcmNlIFdlbGNoIHQtdGVzdCBwcm9jZWR1cmUgYWNyb3NzIGFsbCB0aGUgY2FtcGFpZ25zLgotICoqTWV0aG9kIEMuKiogV2UgdGVzdCBmb3IgdGhlIHBvd2VyLWxhdyBiZWhhdmlvciBpbiB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIChhbW91bnQgb2YgZG9uYXRpb24sIGNhbXBhaWduLXdpc2UgYW5kIGFjcm9zcyB0aGUgQS9CIGxldmVscykgdG8gZmluZCBvdXQgd2hldGhlciB0aGUgc2FtcGxpbmcgZGlzdHJpYnV0aW9uIGlzIE5vcm1hbC4gUmF0aW86IGlmIHRoZSBzYW1wbGluZyBkaXN0cmlidXRpb25zIGFyZSBub3QgTm9ybWFsIChpLmUuIHRoZSBkaXN0cmlidXRpb25zIGV4aGliaXQgcG93ZXItbGF3IGJlaGF2aW9yKSwgd2UgYWJhbmRvbiB0aGUgdC10ZXN0cyBhbmQgbG9vayBmb3Igbm9uLXBhcmFtZXRyaWMgYWx0ZXJuYXRpdmVzIChNYW5uLVdoaXRuZXkgVSB0ZXN0cyk7IGlmIHRoZSBzYW1wbGluZyBkaXN0cmlidXRpb25zIGFyZSBOb3JtYWwsIHdlIGNhbiB2aWV3IGV4dHJlbWVseSBsYXJnZSBkb25hdGlvbiBhbW91bnRzIGFzIG91dGxpZXJzIGFuZCByZXBlYXQgdGhlIHQtdGVzdHMgZm9sbG93aW5nIHRoZWlyIHJlbW92YWwuIEl0IHR1cm5zIG91dCB0aGF0IGluIDMvNCBvZiBzYW1wbGVzIHdlIGNhbiBydWxlIG91dCBwb3dlci1sYXdzIChhc3N1bWluZyBgeF9taW5gIGlzIHRoZSBsb3dlc3Qgb2JzZXJ2YXRpb24gYXQgd2hpY2ggdGhlIHBvd2VyLWxhdyBiZWhhdmlvciBpcyBhc3VtbWVkOyB0cmlja3ksIHdlIHNob3VsZCByZS1ydW4gdGhpcyB3LiBwcmV2aW91cyBlc3RpbWF0aW9uIG9mIGB4X21pbmApLgotICoqTWV0aG9kIEMxLCBDMi4qKiBXZSBwZXJmb3JtIHQtdGVzdHMgYWNjb3JkaW5nIHRvIHRoZSByZXN1bHRzIG9mIExldmVuZSdzIHRlc3RzIChNZXRob2QgQzEpIG9yIGZvcmNlIFdlbGNoIHQtdGVzdHMgKE1ldGhvZCBDMikgKmZvbGxvd2luZyB0aGUgcmVtb3ZhbCBvZiB0aGUgb3V0bGllcnMqLgotICoqTWV0aG9kIEQxLCBEMi4qKiBNYW5uLVdoaXRuZXkgVSB0ZXN0cyBhcmUgcGVyZm9ybWVkIGFzIGEgbm9uLXBhcmFtZXRyaWMgYWx0ZXJuYXRpdmUsIGJlZm9yZSB0aGUgcmVtb3ZhbCBvZiBvdXRsaWVycyAoTWV0aG9kIEQxKSBhbmQgZm9sbG93aW5nIHRoZSByZW1vdmFsIG9mIG91dGxpZXJzIChNZXRob2QgRDIpLgotICoqQ29uY2x1c2lvbi4qKiBBbGwgbWV0aG9kcyBjb252ZXJnZSB0byB0aGUgc2FtZSBjb25jbHVzaW9uIG9mICoqbm8gZWZmZWN0KiouCgoKIyMjIyBNZXRob2QgQS4gUnVuIGluZGVwZW5kZW50IHQtdGVzdHMgb3IgV2VsY2gKClJ1biBpbmRlcGVuZGVudCB0LXRlc3RzIG9yIFdlbGNoLCBhZnRlciB0ZXN0aW5nIGZvciBob21vZ2VuaXR5IG9mIHZhcmlhbmNlcyB3LiBMZXZlbmUncyB0ZXN0LCB3LiBjb3JyZWN0aW9uIGZvciB1bmVxdWFsIHZhcmlhbmNlcyBpZiBuZWNlc3NhcnkKSW4gb3RoZXIgd29yZHMsIHdlIGZpcnN0IHRlc3QgZm9yIHRoZSBob21vZ2VuZWl0eSBvZiB2YXJpYW5jZXMsIGFuZCB0aGUgY2hvb3NlIHRvIGNvbmR1Y3QgYW4gYEluZGVwZW5kZW50YCB0LXRlc3QgaW4gY2FzZSBvZiBob21vZ2VuZWl0eSBvciBgV2VsY2hgIGluIGNhc2Ugb2YgaGV0ZXJvZ2VuZWl0eS4gTm8gbWF0dGVyIHRoZSBuYXR1cmUgb2YgdGhlIHQtdGVzdCwgd2UgcGVyZm9ybSBUT1NUIGVxdWl2YWxlbmNlIHRlc3RpbmcgYWdhaW5zdCB7LjMsIC0uM30gYm91bmRzIGNvbnNpZGVyZWQgYXMgbWluaW1hbCBlZmZlY3Qgc2l6ZS4gVGhlIG1lYXN1cmUgb2YgZWZmZWN0IHNpemUsIGhvd2V2ZXIsIGlzIGNob3NlbiBpbiByZXNwZWN0IHRvIHRoZSB0eXBlIG9mIHRoZSB0LXRlc3QgY29uZHVjdGVkIChDb2hlbidzICoqZCoqIG9yIEdsYXNzJyBkZWx0YSkuCgoqKlJlc3VsdHMuKiogVGhlIGZvbGxvd2luZyB0YWJsZSBzdW1tYXJpemVzIHRoZSB0LXRlc3QgcmVzdWx0czogYGNhbXBhaWduYCBpcyB0aGUgY2FtcGFpZ24ncyBjb2RlLCBgbl9vbGRgIGFuZCBgbl9uZXdgIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGZvciB0aGUgIm5ldyIgYW5kIHRoZSAib2xkIiBwYWdlLCByZXNwZWN0aXZlbHksIGBlcV92YXJpYW5jZWAgaXMgdGhlIGJvb2xlYW4gc2lnbmlmeWluZyB0aGUgb3V0Y29tZSBvZiB0aGUgTGV2ZW5lJ3MgdGVzdCBvZiBlcXVhbGl0eSBvZiB2YXJpYW5jZXMgKGkuZS4gdGhlIHRlc3Qgb2YgaG9tb2dlbmVpdHkgb2YgdmFyaWFuY2UpLCBgdHRlc3RgIHN0YW5kcyBmb3IgdGhlIHQgc3RhdGlzdGljICh0aGUgdC10ZXN0IG91dGNvbWUpLCBgdHRlc3RfcGAgaXMgdGhlIFR5cGUgSSBFcnJvciBwcm9iYWJpbGl0eSAoaS5lLiAqYWxwaGEqKSwgYHR0ZXN0X2RmYCBzdGFuZHMgZm9yIHRoZSByZXNwZWN0aXZlIG51bWJlciBvZiBkZWdyZWVzIG9mIGZyZWVkb20sIGB0dGVzdF9tZXRob2RgIHdvdWxkIGhhdmUgYSB2YWx1ZSBvZiBgSW5kZXBlbmRlbnRgIGZvciBlcXVhbCB2YXJpYW5jZXMgYW5kIGBXZWxjaGAgd2hlbiB0aGUgaG9tb2dlbml0eSBvZiB2YXJpYW5jZSBhc3N1bXB0aW9uIGlzIHZpb2xhdGVkLCBgZWZmX3NpemVgIGlzIHRoZSBtZWFzdXJlIG9mIGVmZmVjdCBzaXplIChOLkIuIENvaGVuJ3MgKipkKiogZm9yIGBJbmRlcGVuZGVudGAgYW5kIEdsYXNzJyBkZWx0YSBmb3IgYFdlbGNoYCksIGBlZmZfbWFnbml0dWRlYCBpcyB0aGUgY2F0ZWdvcml6YXRpb24gb2YgdGhlIGVmZmVjdCBzaXplIChOLkIuIG5vdCBhdmFpbGFibGUgZm9yIEdsYXNzJyBkZWx0YSBzaW5jZSBpdCBpcyB1bmNlcnRhaW4gd2hldGhlciB0aGUgc2FtZSBjYXRlZ29yaWVzIGFwcGx5IGFzIGluIHRoZSBjYXNlIG9mIENvaGVuJ3MgKipkKiopLCBgdGVzdF9lcWAgaXMgdGhlIHJlc3VsdCBvZiB0aGUgVE9TVCBlcXVpdmFsZW5jZSB0ZXN0LiAgCgpgYGB7ciwgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcGFpZ25zIDwtIHVuaXF1ZShkb25hdGlvbnNEYXRhJGNhbXBhaWduKQptZXRob2RBUmVzdWx0cyA8LSBkYXRhLmZyYW1lKAogIGNhbXBhaWduID0gY2hhcmFjdGVyKGxlbmd0aChjYW1wYWlnbnMpKSwKICBuX29sZCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIG5fbmV3ID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgZXFfdmFyaWFuY2UgPSBsb2dpY2FsKGxlbmd0aChjYW1wYWlnbnMpKSwKICB0dGVzdCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIHR0ZXN0X3AgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICB0dGVzdF9kZiA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIHR0ZXN0X21ldGhvZCA9IGNoYXJhY3RlcihsZW5ndGgoY2FtcGFpZ25zKSksCiAgZWZmX3NpemUgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBlZmZfbWFnID0gY2hhcmFjdGVyKGxlbmd0aChjYW1wYWlnbnMpKSwKICB0ZXN0X2VxID0gbG9naWNhbChsZW5ndGgoY2FtcGFpZ25zKSksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEYKKQpmb3IgKGkgaW4gMTpsZW5ndGgoY2FtcGFpZ25zKSkgewogICMgLSBzZWxlY3QgdGVzdERhdGEKICB0ZXN0RGF0YSA8LSBkb25hdGlvbnNEYXRhICU+JSAKICAgIGZpbHRlcihjYW1wYWlnbiAlaW4lIGNhbXBhaWduc1tpXSkgJT4lIAogICAgc2VsZWN0KGV4cEZhY3RvciwgYW1vdW50WWVhcmx5KQogIHRlc3REYXRhJGV4cEZhY3RvciA8LSBmYWN0b3IodGVzdERhdGEkZXhwRmFjdG9yLCBsZXZlbHMgPSBjKCduZXcnLCAnb2xkJykpCiAgIyAtIHRlc3QgZm9yIHRoZSBob21vZ2VuZWl0eSBvZiB2YXJpYW5jZQogIHRlc3RMZXZlbmUgPC0gbGV2ZW5lLnRlc3QodGVzdERhdGEkYW1vdW50WWVhcmx5LCBncm91cCA9IHRlc3REYXRhJGV4cEZhY3RvcikKICBpZiAodGVzdExldmVuZSRwLnZhbHVlID4gLjA1KSB7CiAgICAjIC0gaW5kZXBlbmRlbnQgdC10ZXN0CiAgICB0dGVzdCA8LSB0LnRlc3QodGVzdERhdGEkYW1vdW50WWVhcmx5IH4gdGVzdERhdGEkZXhwRmFjdG9yLCAKICAgICAgICAgICAgICAgICAgICB2YXIuZXF1YWwgPSBUKQogICAgIyAtIGVmZmVjdCBzaXplCiAgICBjb2hlbmQgPC0gY29oZW4uZChkID0gdGVzdERhdGEkYW1vdW50WWVhcmx5LCBmID0gdGVzdERhdGEkZXhwRmFjdG9yLCAKICAgICAgICAgICAgICAgICAgICAgIHBvb2xlZCA9IFQpCiAgICAjIC0gVE9TVAogICAgdG9zdCA8LSBUT1NUdHdvKG0xID0gbWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSksCiAgICAgICAgICAgICAgICAgICAgbTIgPSBtZWFuKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSwKICAgICAgICAgICAgICAgICAgICBzZDEgPSBzZCh0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSksCiAgICAgICAgICAgICAgICAgICAgc2QyID0gc2QodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pLAogICAgICAgICAgICAgICAgICAgIG4xID0gc3VtKHRlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3JyksCiAgICAgICAgICAgICAgICAgICAgbjIgPSAgc3VtKHRlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJyksCiAgICAgICAgICAgICAgICAgICAgbG93X2VxYm91bmRfZCA9IC0uMywKICAgICAgICAgICAgICAgICAgICBoaWdoX2VxYm91bmRfZCA9IC4zLAogICAgICAgICAgICAgICAgICAgIGFscGhhID0gLjA1LAogICAgICAgICAgICAgICAgICAgIHZhci5lcXVhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgcGxvdCA9IEYpCiAgICAjIC0gc3RvcmUgcmVzdWx0cwogICAgbWV0aG9kQVJlc3VsdHMkY2FtcGFpZ25baV0gPC0gY2FtcGFpZ25zW2ldCiAgICBtZXRob2RBUmVzdWx0cyRuX29sZFtpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnKQogICAgbWV0aG9kQVJlc3VsdHMkbl9uZXdbaV0gPC0gc3VtKHRlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3JykKICAgIG1ldGhvZEFSZXN1bHRzJGVxX3ZhcmlhbmNlW2ldIDwtIFQKICAgIG1ldGhvZEFSZXN1bHRzJHR0ZXN0W2ldIDwtIHR0ZXN0JHN0YXRpc3RpYwogICAgbWV0aG9kQVJlc3VsdHMkdHRlc3RfcFtpXSA8LSB0dGVzdCRwLnZhbHVlCiAgICBtZXRob2RBUmVzdWx0cyR0dGVzdF9kZltpXSA8LSB0dGVzdCRwYXJhbWV0ZXIKICAgIG1ldGhvZEFSZXN1bHRzJHR0ZXN0X21ldGhvZFtpXSA8LSAnSW5kZXBlbmRlbnQnCiAgICBtZXRob2RBUmVzdWx0cyRlZmZfc2l6ZVtpXSA8LSBjb2hlbmQkZXN0aW1hdGUKICAgIG1ldGhvZEFSZXN1bHRzJGVmZl9tYWdbaV0gPC0gYXMuY2hhcmFjdGVyKGNvaGVuZCRtYWduaXR1ZGUpCiAgICBtZXRob2RBUmVzdWx0cyR0ZXN0X2VxW2ldIDwtICh0b3N0JFRPU1RfcDEgPCAuMDUgJiB0b3N0JFRPU1RfcDIgPCAuMDUpCiAgfSBlbHNlIHsKICAgICMgLSBwZXJmb3JtIGNvcnJlY3QgdC10ZXN0IGZvciB1bmVxdWFsIHZhcmlhbmNlcwogICAgdHRlc3QgPC0gdC50ZXN0KHRlc3REYXRhJGFtb3VudFllYXJseSB+IHRlc3REYXRhJGV4cEZhY3RvciwgCiAgICAgICAgICAgICAgICAgICAgdmFyLmVxdWFsID0gRikKICAgICMgLSBlZmZlY3Qgc2l6ZQogICAgZ2xhc3NkZWx0YSA8LSAobWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSkgLSAKICAgICAgbWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnXSkpIC8KICAgICAgc2QodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pCiAgICAjIC0gVE9TVAogICAgdG9zdCA8LSBUT1NUdHdvKG0xID0gbWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSksCiAgICAgICAgICAgICAgICAgICAgbTIgPSBtZWFuKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSwKICAgICAgICAgICAgICAgICAgICBzZDEgPSBzZCh0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSksCiAgICAgICAgICAgICAgICAgICAgc2QyID0gc2QodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pLAogICAgICAgICAgICAgICAgICAgIG4xID0gbGVuZ3RoKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldyddKSwKICAgICAgICAgICAgICAgICAgICBuMiA9ICBsZW5ndGgodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pLAogICAgICAgICAgICAgICAgICAgIGxvd19lcWJvdW5kX2QgPSAtLjMsCiAgICAgICAgICAgICAgICAgICAgaGlnaF9lcWJvdW5kX2QgPSAuMywKICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IC4wNSwKICAgICAgICAgICAgICAgICAgICB2YXIuZXF1YWwgPSBGLAogICAgICAgICAgICAgICAgICAgIHBsb3QgPSBGKQogICAgIyAtIHN0b3JlIHJlc3VsdHMKICAgIG1ldGhvZEFSZXN1bHRzJGNhbXBhaWduW2ldIDwtIGNhbXBhaWduc1tpXQogICAgbWV0aG9kQVJlc3VsdHMkbl9vbGRbaV0gPC0gc3VtKHRlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJykKICAgIG1ldGhvZEFSZXN1bHRzJG5fbmV3W2ldIDwtIHN1bSh0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldycpCiAgICBtZXRob2RBUmVzdWx0cyRlcV92YXJpYW5jZVtpXSA8LSBGCiAgICBtZXRob2RBUmVzdWx0cyR0dGVzdFtpXSA8LSB0dGVzdCRzdGF0aXN0aWMKICAgIG1ldGhvZEFSZXN1bHRzJHR0ZXN0X3BbaV0gPC0gdHRlc3QkcC52YWx1ZQogICAgbWV0aG9kQVJlc3VsdHMkdHRlc3RfZGZbaV0gPC0gdHRlc3QkcGFyYW1ldGVyCiAgICBtZXRob2RBUmVzdWx0cyR0dGVzdF9tZXRob2RbaV0gPC0gJ1dlbGNoJwogICAgbWV0aG9kQVJlc3VsdHMkZWZmX3NpemVbaV0gPC0gZ2xhc3NkZWx0YQogICAgbWV0aG9kQVJlc3VsdHMkZWZmX21hZ1tpXSA8LSAnLScKICAgIG1ldGhvZEFSZXN1bHRzJHRlc3RfZXFbaV0gPC0gKHRvc3QkVE9TVF9wMSA8IC4wNSAmIHRvc3QkVE9TVF9wMiA8IC4wNSkKICB9Cn0Ka25pdHI6OmthYmxlKG1ldGhvZEFSZXN1bHRzLCBmb3JtYXQgPSAiaHRtbCIpICU+JSAKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgojIyMjIE1ldGhvZCBCLiBGb3JjZSBXZWxjaCB0LXRlc3RzCgpGb3JjZSBXZWxjaCB0LXRlc3RzLCBhc3N1bWluZyB0aGF0IGlzIHRoZSByZWNvbW1lbmRlZCBwcm9jZWR1cmVzIHdoZW4gZ3JvdXAgc2FtcGxlIHNpemVzIGFyZSBub3QgZXF1YWw7IHRoaXMgaW1wbGllcyBhIGNvcnJlY3Rpb24gZm9yIHVuZXF1YWwgdmFyaWFuY2VzLgoKKipSZXN1bHRzLioqIFRoZSBjb2x1bW5zIGhhdmUgdGhlIHNhbWUgbWVhbmluZyBhcyBpbiB0aGUgcHJldmlvdXMgdGFibGUsIGV4Y2VwdCBmb3IgdGhhdCB0aGUgbWVhc3VyZSBvZiBlZmZlY3RzIHNpemUgaXMgYWx3YXlzIEdsYXNzJyBkZWx0YS4gIAoKYGBge3IsIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBhaWducyA8LSB1bmlxdWUoZG9uYXRpb25zRGF0YSRjYW1wYWlnbikKbWV0aG9kQlJlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICBjYW1wYWlnbiA9IGNoYXJhY3RlcihsZW5ndGgoY2FtcGFpZ25zKSksCiAgbl9vbGQgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBuX25ldyA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIGVxX3ZhcmlhbmNlID0gbG9naWNhbChsZW5ndGgoY2FtcGFpZ25zKSksCiAgdHRlc3QgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICB0dGVzdF9wID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgdHRlc3RfZGYgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICB0dGVzdF9tZXRob2QgPSBjaGFyYWN0ZXIobGVuZ3RoKGNhbXBhaWducykpLAogIGVmZl9zaXplID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgZWZmX21hZyA9IGNoYXJhY3RlcihsZW5ndGgoY2FtcGFpZ25zKSksCiAgdGVzdF9lcSA9IGxvZ2ljYWwobGVuZ3RoKGNhbXBhaWducykpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGCikKZm9yIChpIGluIDE6bGVuZ3RoKGNhbXBhaWducykpIHsKICAjIC0gc2VsZWN0IHRlc3REYXRhCiAgdGVzdERhdGEgPC0gZG9uYXRpb25zRGF0YSAlPiUgCiAgICBmaWx0ZXIoY2FtcGFpZ24gJWluJSBjYW1wYWlnbnNbaV0pICU+JSAKICAgIHNlbGVjdChleHBGYWN0b3IsIGFtb3VudFllYXJseSkKICB0ZXN0RGF0YSRleHBGYWN0b3IgPC0gZmFjdG9yKHRlc3REYXRhJGV4cEZhY3RvciwgbGV2ZWxzID0gYygnbmV3JywgJ29sZCcpKQogICMgLSBwZXJmb3JtIGNvcnJlY3QgdC10ZXN0IGZvciB1bmVxdWFsIHZhcmlhbmNlcwogIHR0ZXN0IDwtIHQudGVzdCh0ZXN0RGF0YSRhbW91bnRZZWFybHkgfiB0ZXN0RGF0YSRleHBGYWN0b3IsIAogICAgICAgICAgICAgICAgICB2YXIuZXF1YWwgPSBGKQogICMgLSBlZmZlY3Qgc2l6ZQogIGdsYXNzZGVsdGEgPC0gKG1lYW4odGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J10pIC0gCiAgICAgICAgICAgICAgICAgICBtZWFuKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSkgLwogICAgc2QodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pCiAgIyAtIFRPU1QKICB0b3N0IDwtIFRPU1R0d28obTEgPSBtZWFuKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldyddKSwKICAgICAgICAgICAgICAgICAgbTIgPSBtZWFuKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSwKICAgICAgICAgICAgICAgICAgc2QxID0gc2QodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J10pLAogICAgICAgICAgICAgICAgICBzZDIgPSBzZCh0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnXSksCiAgICAgICAgICAgICAgICAgIG4xID0gbGVuZ3RoKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldyddKSwKICAgICAgICAgICAgICAgICAgbjIgPSAgbGVuZ3RoKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSwKICAgICAgICAgICAgICAgICAgbG93X2VxYm91bmRfZCA9IC0uMywKICAgICAgICAgICAgICAgICAgaGlnaF9lcWJvdW5kX2QgPSAuMywKICAgICAgICAgICAgICAgICAgYWxwaGEgPSAuMDUsCiAgICAgICAgICAgICAgICAgIHZhci5lcXVhbCA9IEYsCiAgICAgICAgICAgICAgICAgIHBsb3QgPSBGKQogICMgLSBzdG9yZSByZXN1bHRzCiAgbWV0aG9kQlJlc3VsdHMkY2FtcGFpZ25baV0gPC0gY2FtcGFpZ25zW2ldCiAgbWV0aG9kQlJlc3VsdHMkbl9vbGRbaV0gPC0gc3VtKHRlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJykKICBtZXRob2RCUmVzdWx0cyRuX25ld1tpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnKQogIG1ldGhvZEJSZXN1bHRzJGVxX3ZhcmlhbmNlW2ldIDwtIEYKICBtZXRob2RCUmVzdWx0cyR0dGVzdFtpXSA8LSB0dGVzdCRzdGF0aXN0aWMKICBtZXRob2RCUmVzdWx0cyR0dGVzdF9wW2ldIDwtIHR0ZXN0JHAudmFsdWUKICBtZXRob2RCUmVzdWx0cyR0dGVzdF9kZltpXSA8LSB0dGVzdCRwYXJhbWV0ZXIKICBtZXRob2RCUmVzdWx0cyR0dGVzdF9tZXRob2RbaV0gPC0gJ1dlbGNoJwogIG1ldGhvZEJSZXN1bHRzJGVmZl9zaXplW2ldIDwtIGdsYXNzZGVsdGEKICBtZXRob2RCUmVzdWx0cyRlZmZfbWFnW2ldIDwtICctJwogIG1ldGhvZEJSZXN1bHRzJHRlc3RfZXFbaV0gPC0gKHRvc3QkVE9TVF9wMSA8IC4wNSAmIHRvc3QkVE9TVF9wMiA8IC4wNSkKfQprbml0cjo6a2FibGUobWV0aG9kQlJlc3VsdHMsIGZvcm1hdCA9ICJodG1sIikgJT4lIAogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEYsIHBvc2l0aW9uID0gImxlZnQiKQpgYGAKCiMjIyMgTWV0aG9kIEMuIEZpdCBhIHBvd2VyIGxhdyB0byBlYWNoIGRhdGEgc2V0IHRvIGxlYXJuIGFib3V0IHRoZSBuYXR1cmUgb2YgdGhlIHNhbXBsaW5nIGRpc3RyaWJ1dGlvbgoKRml0IGEgcG93ZXIgbGF3IHRvIGVhY2ggZGF0YSBzZXQgdG8gbGVhcm4gYWJvdXQgdGhlIG5hdHVyZSBvZiB0aGUgc2FtcGxpbmcgZGlzdHJpYnV0aW9uOyBpZiB0aGUgcG93ZXIgbGF3IGJlaGF2aW9yIGNhbm5vdCBiZSBleGNsdWRlZCB3ZSBjb25jbHVkZSB0aGF0IHRoZSBzYW1wbGluZyBkaXN0cmlidXRpb24gaXMgbm90IG5vcm1hbCBhbmQgY2hvb3NlIHRvIHByb2NlZWQgd2l0aCBub24tcGFyYW1ldHJpYyB0ZXN0czsgaWYgdGhlIHBvd2VyIGxhdyBjYW4gYmUgZXhjbHVkZWQsIHdlIGNob29zZSB0byByZW1vdmUgb3V0bGllcnMgYW5kIHJlcGVhdCB0aGUgdC10ZXN0cy4KClRlc3QgdGhlIHBvd2VyIGxhdyBiZWhhdmlvciBvZiB0aGUgeWVhcmx5IGFtb3VudCBkaXN0cmlidXRpb25zICoqcGVyIGNhbXBhaWduKiogYW5kICoqcGVyIGV4cGVyaW1lbnRhbCBncm91cCoqLgoqKkNJVEFUSU9OLioqIENvbGluIFMuIEdpbGxlc3BpZSAoMjAxNSkuIEZpdHRpbmcgSGVhdnkgVGFpbGVkIERpc3RyaWJ1dGlvbnM6IFRoZSBwb3dlUmxhdyBQYWNrYWdlLiBKb3VybmFsIG9mIFN0YXRpc3RpY2FsIFNvZnR3YXJlLCA2NCgyKSwgMS0xNi4gVVJMIGh0dHA6Ly93d3cuanN0YXRzb2Z0Lm9yZy92NjQvaTAyLy4KCioqTk9URS4qKiBEbyBub3QgcnVuIHRoaXM7IGl0IGlzIHBhcmFsbGVsaXplZCBhbmQgdGFrZXMgYSB3aGlsZSBvZiB0aW1lIHRvIGNvbXBsZXRlLiAKCmBgYHtyLCBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gRn0KY2FtcGFpZ25zIDwtIHVuaXF1ZShkb25hdGlvbnNEYXRhJGNhbXBhaWduKQojIC0gdGVzdCBwb3dlciBsYXdzCnBsTGlzdCA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKGNhbXBhaWducykpCmZvciAoaSBpbiAxOmxlbmd0aChjYW1wYWlnbnMpKSB7CiAgdGVzdFNldE9sZCA8LSBhcy5pbnRlZ2VyKGRvbmF0aW9uc0RhdGEkYW1vdW50WWVhcmx5W3doaWNoKGRvbmF0aW9uc0RhdGEkY2FtcGFpZ24gPT0gY2FtcGFpZ25zW2ldICYgZG9uYXRpb25zRGF0YSRleHBGYWN0b3IgPT0gJ29sZCcpXSkKICB0ZXN0U2V0TmV3IDwtIGFzLmludGVnZXIoZG9uYXRpb25zRGF0YSRhbW91bnRZZWFybHlbd2hpY2goZG9uYXRpb25zRGF0YSRjYW1wYWlnbiA9PSBjYW1wYWlnbnNbaV0gJiBkb25hdGlvbnNEYXRhJGV4cEZhY3RvciA9PSAnbmV3JyldKQogIG1tVGVzdFNldE9sZCA8LSBkaXNwbCRuZXcodGVzdFNldE9sZCkKICBtbVRlc3RTZXROZXcgPC0gZGlzcGwkbmV3KHRlc3RTZXROZXcpCiAgcGxMaXN0W1tpXV0kb2xkID0gYm9vdHN0cmFwX3AobW1UZXN0U2V0T2xkLCBub19vZl9zaW1zID0gMTAwMCwgdGhyZWFkcyA9IDgpCiAgcGxMaXN0W1tpXV0kbmV3ID0gYm9vdHN0cmFwX3AobW1UZXN0U2V0TmV3LCBub19vZl9zaW1zID0gMTAwMCwgdGhyZWFkcyA9IDgpCn0Kb2xkUFZhbHVlcyA8LSBzYXBwbHkocGxMaXN0LCBmdW5jdGlvbih4KSB7eCRvbGQkcH0pCm5ld1BWYWx1ZXMgPC0gc2FwcGx5KHBsTGlzdCwgZnVuY3Rpb24oeCkge3gkbmV3JHB9KQpgYGAKCioqUmVzdWx0cy4qKiBXaXRoIDMgb3V0IG9mIDEyIGRhdGEgc2V0cyBleGhpYml0aW5nIHBvd2VyIGxhdyBiZWhhdmlvciB3LiBgeF9taW5gIHNldCB0byB0aGUgbG93ZXN0IHZhbHVlIGluIHRoZSBkYXRhIHNldCwgd2UgY2hvb3NlIHRvIHJlbW92ZSB0aGUgb3V0bGllcnMgYW5kIHJlcGVhdCB0aGUgdC10ZXN0cy4KKipOT1RFLioqIFJlLXJ1bm5pbmcgdGhpcyB3aXRoIGEgcHJldmlvdXMgZXN0aW1hdGlvbiBvZiBgeF9taW5gIHdvdWxkbid0IGh1cnQuCgoKIyMjIyBNZXRob2QgQzEuIFJ1biBpbmRlcGVuZGVudCB0LXRlc3RzIG9yIFdlbGNoIGZvbGxvd2luZyB0aGUgcmVtb3ZhbCBvZiBvdXRsaWVycwoKUnVuIGluZGVwZW5kZW50IHQtdGVzdHMgb3IgV2VsY2gsIGFmdGVyIHRlc3RpbmcgZm9yIGhvbW9nZW5pdHkgb2YgdmFyaWFuY2VzIHcuIExldmVuZSdzIHRlc3QsIHcuIGNvcnJlY3Rpb24gZm9yIHVuZXF1YWwgdmFyaWFuY2VzIGlmIG5lY2Vzc2FyeSwgYW5kICAqKmZvbGxvd2luZyB0aGUgcmVtb3ZhbCBvZiBvdXRsaWVycyoqLgoKKipOT1RFLioqIE9ubHkgZXh0cmVtZSBvdXRsaWVycyBvbiB0aGUgdXBwZXIgdGFpbCBvZiB0aGUgZGlzdHJpYnV0aW9uICg+IGAzICogSVFSYCwgYElRUmAgPT0gaW50ZXJxdWFydGlsZSByYW5nZSkgd2VyZSByZW1vdmVkLiBJbiBlZmZlY3QsIGV4dHJlbWVseSBoaWdoIHllYXJseSBkb25hdGlvbiBhbW91bnRzIHdlcmUgcmVtb3ZlZC4KCmBgYHtyLCBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wYWlnbnMgPC0gdW5pcXVlKGRvbmF0aW9uc0RhdGEkY2FtcGFpZ24pCm1ldGhvZEMxUmVzdWx0cyA8LSBkYXRhLmZyYW1lKAogIGNhbXBhaWduID0gY2hhcmFjdGVyKGxlbmd0aChjYW1wYWlnbnMpKSwKICBuX29sZCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIG5fbmV3ID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgZXFfdmFyaWFuY2UgPSBsb2dpY2FsKGxlbmd0aChjYW1wYWlnbnMpKSwKICB0dGVzdCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIHR0ZXN0X3AgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICB0dGVzdF9kZiA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIHR0ZXN0X21ldGhvZCA9IGNoYXJhY3RlcihsZW5ndGgoY2FtcGFpZ25zKSksCiAgZWZmX3NpemUgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBlZmZfbWFnID0gY2hhcmFjdGVyKGxlbmd0aChjYW1wYWlnbnMpKSwKICB0ZXN0X2VxID0gbG9naWNhbChsZW5ndGgoY2FtcGFpZ25zKSksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEYKKQpmb3IgKGkgaW4gMTpsZW5ndGgoY2FtcGFpZ25zKSkgewogICMgLSBzZWxlY3QgdGVzdERhdGEKICB0ZXN0RGF0YSA8LSBkb25hdGlvbnNEYXRhICU+JSAKICAgIGZpbHRlcihjYW1wYWlnbiAlaW4lIGNhbXBhaWduc1tpXSkgJT4lIAogICAgc2VsZWN0KGV4cEZhY3RvciwgYW1vdW50WWVhcmx5KQogIHRlc3REYXRhJGV4cEZhY3RvciA8LSBmYWN0b3IodGVzdERhdGEkZXhwRmFjdG9yLCBsZXZlbHMgPSBjKCduZXcnLCAnb2xkJykpCiAgIyAtIHJlbW92ZSBvdXRsaWVyczogb25lLXNpZGVkLCA+IDMqSVFSIHBlciBncm91cAogIHRlc3REYXRhJG91dGxpZXIgPC0gbG9naWNhbChkaW0odGVzdERhdGEpWzFdKQogIHdOZXdPdXQgPC0gd2hpY2godGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J10gPgogICAgICAgICAgICAgICAgICAgICAzICogSVFSKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldyddKSkKICB0ZXN0RGF0YSRvdXRsaWVyW3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J11bd05ld091dF0gPC0gVAogIHdPbGRPdXQgPC0gd2hpY2godGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10gPgogICAgICAgICAgICAgICAgICAgICAzICogSVFSKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSkKICB0ZXN0RGF0YSRvdXRsaWVyW3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ11bd09sZE91dF0gPC0gVAogIHRlc3REYXRhIDwtIGZpbHRlcih0ZXN0RGF0YSwgb3V0bGllciA9PSBGKQogICMgLSB0ZXN0IGZvciB0aGUgaG9tb2dlbmVpdHkgb2YgdmFyaWFuY2UKICB0ZXN0TGV2ZW5lIDwtIGxldmVuZS50ZXN0KHRlc3REYXRhJGFtb3VudFllYXJseSwgZ3JvdXAgPSB0ZXN0RGF0YSRleHBGYWN0b3IpCiAgaWYgKHRlc3RMZXZlbmUkcC52YWx1ZSA+IC4wNSkgewogICAgIyAtIGluZGVwZW5kZW50IHQtdGVzdAogICAgdHRlc3QgPC0gdC50ZXN0KHRlc3REYXRhJGFtb3VudFllYXJseSB+IHRlc3REYXRhJGV4cEZhY3RvciwgCiAgICAgICAgICAgICAgICAgICAgdmFyLmVxdWFsID0gVCkKICAgICMgLSBlZmZlY3Qgc2l6ZQogICAgY29oZW5kIDwtIGNvaGVuLmQoZCA9IHRlc3REYXRhJGFtb3VudFllYXJseSwgZiA9IHRlc3REYXRhJGV4cEZhY3RvciwgCiAgICAgICAgICAgICAgICAgICAgICBwb29sZWQgPSBUKQogICAgIyAtIFRPU1QKICAgIHRvc3QgPC0gVE9TVHR3byhtMSA9IG1lYW4odGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J10pLAogICAgICAgICAgICAgICAgICAgIG0yID0gbWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnXSksCiAgICAgICAgICAgICAgICAgICAgc2QxID0gc2QodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J10pLAogICAgICAgICAgICAgICAgICAgIHNkMiA9IHNkKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSwKICAgICAgICAgICAgICAgICAgICBuMSA9IHN1bSh0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldycpLAogICAgICAgICAgICAgICAgICAgIG4yID0gIHN1bSh0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCcpLAogICAgICAgICAgICAgICAgICAgIGxvd19lcWJvdW5kX2QgPSAtLjMsCiAgICAgICAgICAgICAgICAgICAgaGlnaF9lcWJvdW5kX2QgPSAuMywKICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IC4wNSwKICAgICAgICAgICAgICAgICAgICB2YXIuZXF1YWwgPSBULAogICAgICAgICAgICAgICAgICAgIHBsb3QgPSBGKQogICAgIyAtIHN0b3JlIHJlc3VsdHMKICAgIG1ldGhvZEMxUmVzdWx0cyRjYW1wYWlnbltpXSA8LSBjYW1wYWlnbnNbaV0KICAgIG1ldGhvZEMxUmVzdWx0cyRuX29sZFtpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnKQogICAgbWV0aG9kQzFSZXN1bHRzJG5fbmV3W2ldIDwtIHN1bSh0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldycpCiAgICBtZXRob2RDMVJlc3VsdHMkZXFfdmFyaWFuY2VbaV0gPC0gVAogICAgbWV0aG9kQzFSZXN1bHRzJHR0ZXN0W2ldIDwtIHR0ZXN0JHN0YXRpc3RpYwogICAgbWV0aG9kQzFSZXN1bHRzJHR0ZXN0X3BbaV0gPC0gdHRlc3QkcC52YWx1ZQogICAgbWV0aG9kQzFSZXN1bHRzJHR0ZXN0X2RmW2ldIDwtIHR0ZXN0JHBhcmFtZXRlcgogICAgbWV0aG9kQzFSZXN1bHRzJHR0ZXN0X21ldGhvZFtpXSA8LSAnSW5kZXBlbmRlbnQnCiAgICBtZXRob2RDMVJlc3VsdHMkZWZmX3NpemVbaV0gPC0gY29oZW5kJGVzdGltYXRlCiAgICBtZXRob2RDMVJlc3VsdHMkZWZmX21hZ1tpXSA8LSBhcy5jaGFyYWN0ZXIoY29oZW5kJG1hZ25pdHVkZSkKICAgIG1ldGhvZEMxUmVzdWx0cyR0ZXN0X2VxW2ldIDwtICh0b3N0JFRPU1RfcDEgPCAuMDUgJiB0b3N0JFRPU1RfcDIgPCAuMDUpCiAgfSBlbHNlIHsKICAgICMgLSBwZXJmb3JtIGNvcnJlY3QgdC10ZXN0IGZvciB1bmVxdWFsIHZhcmlhbmNlcwogICAgdHRlc3QgPC0gdC50ZXN0KHRlc3REYXRhJGFtb3VudFllYXJseSB+IHRlc3REYXRhJGV4cEZhY3RvciwgCiAgICAgICAgICAgICAgICAgICAgdmFyLmVxdWFsID0gRikKICAgICMgLSBlZmZlY3Qgc2l6ZQogICAgZ2xhc3NkZWx0YSA8LSAobWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSkgLSAKICAgICAgICAgICAgICAgICAgICAgbWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnXSkpIC8KICAgICAgc2QodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pCiAgICAjIC0gVE9TVAogICAgdG9zdCA8LSBUT1NUdHdvKG0xID0gbWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSksCiAgICAgICAgICAgICAgICAgICAgbTIgPSBtZWFuKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSwKICAgICAgICAgICAgICAgICAgICBzZDEgPSBzZCh0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSksCiAgICAgICAgICAgICAgICAgICAgc2QyID0gc2QodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pLAogICAgICAgICAgICAgICAgICAgIG4xID0gbGVuZ3RoKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldyddKSwKICAgICAgICAgICAgICAgICAgICBuMiA9ICBsZW5ndGgodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pLAogICAgICAgICAgICAgICAgICAgIGxvd19lcWJvdW5kX2QgPSAtLjMsCiAgICAgICAgICAgICAgICAgICAgaGlnaF9lcWJvdW5kX2QgPSAuMywKICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IC4wNSwKICAgICAgICAgICAgICAgICAgICB2YXIuZXF1YWwgPSBGLAogICAgICAgICAgICAgICAgICAgIHBsb3QgPSBGKQogICAgIyAtIHN0b3JlIHJlc3VsdHMKICAgIG1ldGhvZEMxUmVzdWx0cyRjYW1wYWlnbltpXSA8LSBjYW1wYWlnbnNbaV0KICAgIG1ldGhvZEMxUmVzdWx0cyRuX29sZFtpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnKQogICAgbWV0aG9kQzFSZXN1bHRzJG5fbmV3W2ldIDwtIHN1bSh0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldycpCiAgICBtZXRob2RDMVJlc3VsdHMkZXFfdmFyaWFuY2VbaV0gPC0gRgogICAgbWV0aG9kQzFSZXN1bHRzJHR0ZXN0W2ldIDwtIHR0ZXN0JHN0YXRpc3RpYwogICAgbWV0aG9kQzFSZXN1bHRzJHR0ZXN0X3BbaV0gPC0gdHRlc3QkcC52YWx1ZQogICAgbWV0aG9kQzFSZXN1bHRzJHR0ZXN0X2RmW2ldIDwtIHR0ZXN0JHBhcmFtZXRlcgogICAgbWV0aG9kQzFSZXN1bHRzJHR0ZXN0X21ldGhvZFtpXSA8LSAnV2VsY2gnCiAgICBtZXRob2RDMVJlc3VsdHMkZWZmX3NpemVbaV0gPC0gZ2xhc3NkZWx0YQogICAgbWV0aG9kQzFSZXN1bHRzJGVmZl9tYWdbaV0gPC0gJy0nCiAgICBtZXRob2RDMVJlc3VsdHMkdGVzdF9lcVtpXSA8LSAodG9zdCRUT1NUX3AxIDwgLjA1ICYgdG9zdCRUT1NUX3AyIDwgLjA1KQogIH0KfQprbml0cjo6a2FibGUobWV0aG9kQzFSZXN1bHRzLCBmb3JtYXQgPSAiaHRtbCIpICU+JSAKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgojIyMjIE1ldGhvZCBDMi4gRm9yY2UgV2VsY2ggdC10ZXN0IGZvbGxvd2luZyB0aGUgcmVtb3ZhbCBvZiBvdXRsaWVycwoKRm9yY2UgV2VsY2ggdC10ZXN0LCBhZnRlciB0ZXN0aW5nIGZvciBob21vZ2VuaXR5IG9mIHZhcmlhbmNlcyB3LiBMZXZlbmUncyB0ZXN0LCB3LiBjb3JyZWN0aW9uIGZvciB1bmVxdWFsIHZhcmlhbmNlcyBpZiBuZWNlc3NhcnksIGFuZCAgKipmb2xsb3dpbmcgdGhlIHJlbW92YWwgb2Ygb3V0bGllcnMqKi4KCioqTk9URS4qKiBPbmx5IGV4dHJlbWUgb3V0bGllcnMgb24gdGhlIHVwcGVyIHRhaWwgb2YgdGhlIGRpc3RyaWJ1dGlvbiAoPiBgMyAqIElRUmAsIGBJUVJgID09IGludGVycXVhcnRpbGUgcmFuZ2UpIHdlcmUgcmVtb3ZlZC4gSW4gZWZmZWN0LCBleHRyZW1lbHkgaGlnaCB5ZWFybHkgZG9uYXRpb24gYW1vdW50cyB3ZXJlIHJlbW92ZWQuCgoqKk5PVEUuKiogVGhlIGVmZmVjdCBzaXplIChgZWZmX3NpemVgKSBpcyBhbHdheXMgR2xhc3MnIGRlbHRhLgoKYGBge3IsIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBhaWducyA8LSB1bmlxdWUoZG9uYXRpb25zRGF0YSRjYW1wYWlnbikKbWV0aG9kQzJSZXN1bHRzIDwtIGRhdGEuZnJhbWUoCiAgY2FtcGFpZ24gPSBjaGFyYWN0ZXIobGVuZ3RoKGNhbXBhaWducykpLAogIG5fb2xkID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgbl9uZXcgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBlcV92YXJpYW5jZSA9IGxvZ2ljYWwobGVuZ3RoKGNhbXBhaWducykpLAogIHR0ZXN0ID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgdHRlc3RfcCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIHR0ZXN0X2RmID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgdHRlc3RfbWV0aG9kID0gY2hhcmFjdGVyKGxlbmd0aChjYW1wYWlnbnMpKSwKICBlZmZfc2l6ZSA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIGVmZl9tYWcgPSBjaGFyYWN0ZXIobGVuZ3RoKGNhbXBhaWducykpLAogIHRlc3RfZXEgPSBsb2dpY2FsKGxlbmd0aChjYW1wYWlnbnMpKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRgopCmZvciAoaSBpbiAxOmxlbmd0aChjYW1wYWlnbnMpKSB7CiAgIyAtIHNlbGVjdCB0ZXN0RGF0YQogIHRlc3REYXRhIDwtIGRvbmF0aW9uc0RhdGEgJT4lIAogICAgZmlsdGVyKGNhbXBhaWduICVpbiUgY2FtcGFpZ25zW2ldKSAlPiUgCiAgICBzZWxlY3QoZXhwRmFjdG9yLCBhbW91bnRZZWFybHkpCiAgdGVzdERhdGEkZXhwRmFjdG9yIDwtIGZhY3Rvcih0ZXN0RGF0YSRleHBGYWN0b3IsIGxldmVscyA9IGMoJ25ldycsICdvbGQnKSkKICAjIC0gcmVtb3ZlIG91dGxpZXJzOiBvbmUtc2lkZWQsID4gMypJUVIgcGVyIGdyb3VwCiAgdGVzdERhdGEkb3V0bGllciA8LSBsb2dpY2FsKGRpbSh0ZXN0RGF0YSlbMV0pCiAgd05ld091dCA8LSB3aGljaCh0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSA+CiAgICAgICAgICAgICAgICAgICAgIDMgKiBJUVIodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J10pKQogIHRlc3REYXRhJG91dGxpZXJbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXVt3TmV3T3V0XSA8LSBUCiAgd09sZE91dCA8LSB3aGljaCh0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnXSA+CiAgICAgICAgICAgICAgICAgICAgIDMgKiBJUVIodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pKQogIHRlc3REYXRhJG91dGxpZXJbdGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnXVt3T2xkT3V0XSA8LSBUCiAgdGVzdERhdGEgPC0gZmlsdGVyKHRlc3REYXRhLCBvdXRsaWVyID09IEYpCiAgIyAtIHBlcmZvcm0gY29ycmVjdCB0LXRlc3QgZm9yIHVuZXF1YWwgdmFyaWFuY2VzCiAgdHRlc3QgPC0gdC50ZXN0KHRlc3REYXRhJGFtb3VudFllYXJseSB+IHRlc3REYXRhJGV4cEZhY3RvciwgCiAgICAgICAgICAgICAgICAgIHZhci5lcXVhbCA9IEYpCiAgIyAtIGVmZmVjdCBzaXplCiAgZ2xhc3NkZWx0YSA8LSAobWVhbih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSkgLSAKICAgICAgICAgICAgICAgICAgIG1lYW4odGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pKSAvCiAgICBzZCh0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnXSkKICAjIC0gVE9TVAogIHRvc3QgPC0gVE9TVHR3byhtMSA9IG1lYW4odGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J10pLAogICAgICAgICAgICAgICAgICBtMiA9IG1lYW4odGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pLAogICAgICAgICAgICAgICAgICBzZDEgPSBzZCh0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSksCiAgICAgICAgICAgICAgICAgIHNkMiA9IHNkKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddKSwKICAgICAgICAgICAgICAgICAgbjEgPSBsZW5ndGgodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3J10pLAogICAgICAgICAgICAgICAgICBuMiA9ICBsZW5ndGgodGVzdERhdGEkYW1vdW50WWVhcmx5W3Rlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJ10pLAogICAgICAgICAgICAgICAgICBsb3dfZXFib3VuZF9kID0gLS4zLAogICAgICAgICAgICAgICAgICBoaWdoX2VxYm91bmRfZCA9IC4zLAogICAgICAgICAgICAgICAgICBhbHBoYSA9IC4wNSwKICAgICAgICAgICAgICAgICAgdmFyLmVxdWFsID0gRiwKICAgICAgICAgICAgICAgICAgcGxvdCA9IEYpCiAgIyAtIHN0b3JlIHJlc3VsdHMKICBtZXRob2RDMlJlc3VsdHMkY2FtcGFpZ25baV0gPC0gY2FtcGFpZ25zW2ldCiAgbWV0aG9kQzJSZXN1bHRzJG5fb2xkW2ldIDwtIHN1bSh0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCcpCiAgbWV0aG9kQzJSZXN1bHRzJG5fbmV3W2ldIDwtIHN1bSh0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldycpCiAgbWV0aG9kQzJSZXN1bHRzJGVxX3ZhcmlhbmNlW2ldIDwtIEYKICBtZXRob2RDMlJlc3VsdHMkdHRlc3RbaV0gPC0gdHRlc3Qkc3RhdGlzdGljCiAgbWV0aG9kQzJSZXN1bHRzJHR0ZXN0X3BbaV0gPC0gdHRlc3QkcC52YWx1ZQogIG1ldGhvZEMyUmVzdWx0cyR0dGVzdF9kZltpXSA8LSB0dGVzdCRwYXJhbWV0ZXIKICBtZXRob2RDMlJlc3VsdHMkdHRlc3RfbWV0aG9kW2ldIDwtICdXZWxjaCcKICBtZXRob2RDMlJlc3VsdHMkZWZmX3NpemVbaV0gPC0gZ2xhc3NkZWx0YQogIG1ldGhvZEMyUmVzdWx0cyRlZmZfbWFnW2ldIDwtICctJwogIG1ldGhvZEMyUmVzdWx0cyR0ZXN0X2VxW2ldIDwtICh0b3N0JFRPU1RfcDEgPCAuMDUgJiB0b3N0JFRPU1RfcDIgPCAuMDUpCn0Ka25pdHI6OmthYmxlKG1ldGhvZEMyUmVzdWx0cywgZm9ybWF0ID0gImh0bWwiKSAlPiUgCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKIyMjIyBNZXRob2QgRDEuIE5vbi1wYXJhbWV0cmljIHRlc3RzOiBNYW5uLVdoaXRuZXkgVSBUZXN0LCBubyByZW1vdmFsIG9mIG91dGxpZXJzCgpDb25kdWN0IG5vbi1wYXJhbWV0cmljLCBpbmRlcGVuZGVudCAyLWdyb3VwIE1hbm4tV2hpdG5leSBVIFRlc3Q7IG5vIHJlbW92YWwgb2Ygb3V0bGllcnMuCgoqKlJlc3VsdHMuKiogYGNhbXBhaWduYCBzdGFuZHMgZm9yIHRoZSBjYW1wYWlnbiBjb2RlLCBgbl9vbGRgIGFuZCBgbl9uZXdgIGFyZSB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBmb3IgdGhlICJuZXciIGFuZCB0aGUgIm9sZCIgcGFnZSByZXNwZWN0aXZlbHksIGBXYCBpcyB0aGUgV2lsY294b24gdGVzdCBzdGF0aXN0aWNzLCBhbmQgYE1XVVRlc3RfcGAgdGhlIGFzc29jaWF0ZWQgcHJvYmFiaWxpdHkgb2YgdGhlIFR5cGUgSSBFcnJvci4gCgpgYGB7ciwgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcGFpZ25zIDwtIHVuaXF1ZShkb25hdGlvbnNEYXRhJGNhbXBhaWduKQptZXRob2REMVJlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICBjYW1wYWlnbiA9IGNoYXJhY3RlcihsZW5ndGgoY2FtcGFpZ25zKSksCiAgbl9vbGQgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBuX25ldyA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIFcgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBNV1VUZXN0X3AgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRgopCmZvciAoaSBpbiAxOmxlbmd0aChjYW1wYWlnbnMpKSB7CiAgIyAtIHNlbGVjdCB0ZXN0RGF0YQogIHRlc3REYXRhIDwtIGRvbmF0aW9uc0RhdGEgJT4lIAogICAgZmlsdGVyKGNhbXBhaWduICVpbiUgY2FtcGFpZ25zW2ldKSAlPiUgCiAgICBzZWxlY3QoZXhwRmFjdG9yLCBhbW91bnRZZWFybHkpCiAgdGVzdERhdGEkZXhwRmFjdG9yIDwtIGZhY3Rvcih0ZXN0RGF0YSRleHBGYWN0b3IsIGxldmVscyA9IGMoJ25ldycsICdvbGQnKSkKICAjIC0gbm9uLXBhcmFtZXRyaWMgdGVzdAogIG13dSA8LSB3aWxjb3gudGVzdCh0ZXN0RGF0YSRhbW91bnRZZWFybHkgfiB0ZXN0RGF0YSRleHBGYWN0b3IpIAogICMgLSBzdG9yZSByZXN1bHRzCiAgbWV0aG9kRDFSZXN1bHRzJGNhbXBhaWduW2ldIDwtIGNhbXBhaWduc1tpXQogIG1ldGhvZEQxUmVzdWx0cyRuX29sZFtpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnKQogIG1ldGhvZEQxUmVzdWx0cyRuX25ld1tpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnKQogIG1ldGhvZEQxUmVzdWx0cyRXW2ldIDwtIG13dSRzdGF0aXN0aWMKICBtZXRob2REMVJlc3VsdHMkTVdVVGVzdF9wW2ldIDwtIG13dSRwLnZhbHVlCn0Ka25pdHI6OmthYmxlKG1ldGhvZEQxUmVzdWx0cywgZm9ybWF0ID0gImh0bWwiKSAlPiUgCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKIyMjIyBNZXRob2QgRDIuIE5vbi1wYXJhbWV0cmljIHRlc3RzOiBNYW5uLVdoaXRuZXkgVSBUZXN0LCBmb2xsb3dpbmcgdGhlIHJlbW92YWwgb2Ygb3V0bGllcnMKCkNvbmR1Y3Qgbm9uLXBhcmFtZXRyaWMsIGluZGVwZW5kZW50IDItZ3JvdXAgTWFubi1XaGl0bmV5IFUgVGVzdCwgZm9sbG93aW5nIHRoZSByZW1vdmFsIG9mIG91dGxpZXJzLgoKKipOT1RFLioqIE9ubHkgZXh0cmVtZSBvdXRsaWVycyBvbiB0aGUgdXBwZXIgdGFpbCBvZiB0aGUgZGlzdHJpYnV0aW9uICg+IGAzICogSVFSYCwgYElRUmAgPT0gaW50ZXJxdWFydGlsZSByYW5nZSkgd2VyZSByZW1vdmVkLiBJbiBlZmZlY3QsIGV4dHJlbWVseSBoaWdoIHllYXJseSBkb25hdGlvbiBhbW91bnRzIHdlcmUgcmVtb3ZlZC4KCmBgYHtyLCBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wYWlnbnMgPC0gdW5pcXVlKGRvbmF0aW9uc0RhdGEkY2FtcGFpZ24pCm1ldGhvZEQyUmVzdWx0cyA8LSBkYXRhLmZyYW1lKAogIGNhbXBhaWduID0gY2hhcmFjdGVyKGxlbmd0aChjYW1wYWlnbnMpKSwKICBuX29sZCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIG5fbmV3ID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgVyA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIE1XVVRlc3RfcCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGCikKZm9yIChpIGluIDE6bGVuZ3RoKGNhbXBhaWducykpIHsKICAjIC0gc2VsZWN0IHRlc3REYXRhCiAgdGVzdERhdGEgPC0gZG9uYXRpb25zRGF0YSAlPiUgCiAgICBmaWx0ZXIoY2FtcGFpZ24gJWluJSBjYW1wYWlnbnNbaV0pICU+JSAKICAgIHNlbGVjdChleHBGYWN0b3IsIGFtb3VudFllYXJseSkKICB0ZXN0RGF0YSRleHBGYWN0b3IgPC0gZmFjdG9yKHRlc3REYXRhJGV4cEZhY3RvciwgbGV2ZWxzID0gYygnbmV3JywgJ29sZCcpKQogICMgLSByZW1vdmUgb3V0bGllcnM6IG9uZS1zaWRlZCwgPiAzKklRUiBwZXIgZ3JvdXAKICB0ZXN0RGF0YSRvdXRsaWVyIDwtIGxvZ2ljYWwoZGltKHRlc3REYXRhKVsxXSkKICB3TmV3T3V0IDwtIHdoaWNoKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldyddID4KICAgICAgICAgICAgICAgICAgICAgMyAqIElRUih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnXSkpCiAgdGVzdERhdGEkb3V0bGllclt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ25ldyddW3dOZXdPdXRdIDwtIFQKICB3T2xkT3V0IDwtIHdoaWNoKHRlc3REYXRhJGFtb3VudFllYXJseVt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddID4KICAgICAgICAgICAgICAgICAgICAgMyAqIElRUih0ZXN0RGF0YSRhbW91bnRZZWFybHlbdGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnXSkpCiAgdGVzdERhdGEkb3V0bGllclt0ZXN0RGF0YSRleHBGYWN0b3IgPT0gJ29sZCddW3dPbGRPdXRdIDwtIFQKICB0ZXN0RGF0YSA8LSBmaWx0ZXIodGVzdERhdGEsIG91dGxpZXIgPT0gRikKICAjIC0gbm9uLXBhcmFtZXRyaWMgdGVzdAogIG13dSA8LSB3aWxjb3gudGVzdCh0ZXN0RGF0YSRhbW91bnRZZWFybHkgfiB0ZXN0RGF0YSRleHBGYWN0b3IpIAogICMgLSBzdG9yZSByZXN1bHRzCiAgbWV0aG9kRDJSZXN1bHRzJGNhbXBhaWduW2ldIDwtIGNhbXBhaWduc1tpXQogIG1ldGhvZEQyUmVzdWx0cyRuX29sZFtpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICdvbGQnKQogIG1ldGhvZEQyUmVzdWx0cyRuX25ld1tpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnKQogIG1ldGhvZEQyUmVzdWx0cyRXW2ldIDwtIG13dSRzdGF0aXN0aWMKICBtZXRob2REMlJlc3VsdHMkTVdVVGVzdF9wW2ldIDwtIG13dSRwLnZhbHVlCn0Ka25pdHI6OmthYmxlKG1ldGhvZEQyUmVzdWx0cywgZm9ybWF0ID0gImh0bWwiKSAlPiUgCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKIyMjIDEuNiBBL0IgdGVzdGluZyAocGVyIGNhbXBhaWduKTogT3B0IGluCgojIyMjIE91dGxpbmUKCkJpbmFyeSBsb2dpc3RpYyByZWdyZXNzaW9uIHdhcyBhIG1ldGhvZCBvZiBvdXIgY2hvaWNlLCB3aXRoIHRoZSBleHBlcmltZW50YWwgQS9CIGZhY3RvciAoIm5ldyIvIm9sZCIpIGFzIGEgcHJlZGljdG9yIG9mIGBvcHRfaW5gLiBUaGUgIm9sZCIgbGV2ZWwgb2YgdGhlIGV4cGVyaW1lbnRhbCBmYWN0b3Igd2FzIHVzZWQgYXMgYSBiYXNlbGluZS4KKipDb25jbHVzaW9uLioqIEV4Y2VwdCBmb3IgdGhlIGBtb2IwNS1iYS0xNzEyMThgIGNhbXBhaWduIHdoZXJlIHRoZSBleHBlcmltZW50YWwgZmFjdG9yIGRpZCBub3QgaGF2ZSBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZWZmZWN0IHVwb24gYG9wdF9pbmAsIHRoZSBuZXcgcGFnZSBzZWVtcyB0byBhbHdheXMgaW5kdWNlIGEgcmVkdWN0aW9uIGluIHRoZSBwcm9iYWJpbGl0eSB0byBvcHRfaW4uCgojIyMjIEJpbmFyeSBMb2dpc3RpYyBSZWdyZXNzaW9uOiBBL0IgdnMuIE9wdCBpbgoKKipSZXN1bHRzLioqIGBjYW1wYWlnbmAgc3RhbmRzIGZvciB0aGUgY2FtcGFpZ24gY29kZSwgYG5fb2xkYCBhbmQgYG5fbmV3YCB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBmb3IgdGhlICJuZXciIGFuZCB0aGUgIm9sZCIgcGFnZSwgcmVzcGVjdGl2ZWx5LCBgY29lZmZgIGlzIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50IGZvciB0aGUgQS9CIGV4cGVyaW1lbnRhbCBmYWN0b3IgKCoqTk9URToqKiB1c2luZyBgb2xkYCBhcyBhIGJhc2VsaW5lKSwgYGV4cF9jb2VmZmAgaXMgdGhlIGV4cG9uZW50aWFsIG9mIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50IC0gdGhlIGRlbHRhIG9kZHMsIHNob3VsZCBiZSBpbnRlcnByZXRlZCBhcyB0aGUgaW5jcmVhc2UgaW4gdGhlIG9kZHMgb2Ygb3B0aW5nIGluIHZzLiBub3Qgb3B0aW5nIGluIHdpdGggYSB1bml0IGluY3JlYXNlIGluIHRoZSBleHBlcmltZW50YWwgZmFjdG9ycyB3aGljaCBpbiBvdXIgY2FzZSBtZWFucyBzd2l0Y2hpbmcgZnJvbSB0aGUgYG9sZGAgdG8gdGhlIGBuZXdgIHBhZ2UsIGBjb2VmZl96YCBpcyB0aGUgdmFsdWUgb2YgdGhlIFdhbGQncyB0ZXN0IChzaW1pbGFyIHRvIHQtdGVzdCBpbiBsaW5lYXIgcmVncmVzc2lvbiwgdGVzdHMgaWYgdGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnQgaXMgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZnJvbSB6ZXJvKSwgYGNvZWZmX3BgIHRoZSByZXNwZWN0aXZlIHByb2JhYmlsaXR5IG9mIHRoZSBUeXBlIEkgRXJyb3IsIGBzaWdgIHdoZXRoZXIgdGhlIFdhbGQncyB0ZXN0IGhhdmUgcmVhY2hlZCB0aGUgY29udmVudGlvbmFsIGBhbHBoYSA8IC4wNWAgc2lnbmlmaWNhbmNlIGNyaXRlcmlvbi4KCmBgYHtyLCBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wYWlnbnMgPC0gdW5pcXVlKGRvbmF0aW9uc0RhdGEkY2FtcGFpZ24pCm9wdF9pbl9CTFJfUmVzdWx0cyA8LSBkYXRhLmZyYW1lKAogIGNhbXBhaWduID0gY2hhcmFjdGVyKGxlbmd0aChjYW1wYWlnbnMpKSwKICBuX29sZCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIG5fbmV3ID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgY29lZmYgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBleHBfY29lZmYgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBjb2VmZl96ID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgY29lZmZfcCA9IG51bWVyaWMobGVuZ3RoKGNhbXBhaWducykpLAogIHNpZyA9IGxvZ2ljYWwobGVuZ3RoKGNhbXBhaWducykpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGCikKZm9yIChpIGluIDE6bGVuZ3RoKGNhbXBhaWducykpIHsKICAjIC0gc2VsZWN0IHRlc3REYXRhCiAgdGVzdERhdGEgPC0gZG9uYXRpb25zRGF0YSAlPiUgCiAgICBmaWx0ZXIoY2FtcGFpZ24gJWluJSBjYW1wYWlnbnNbaV0pICU+JSAKICAgIHNlbGVjdChleHBGYWN0b3IsIGFtb3VudFllYXJseSwgb3B0X2luKQogIHRlc3REYXRhJGV4cEZhY3RvciA8LSBmYWN0b3IodGVzdERhdGEkZXhwRmFjdG9yLCBsZXZlbHMgPSBjKCdvbGQnLCAnbmV3JykpCiAgIyAtIEJpbmFyeSBsb2dpc3RpYyByZWdyZXNzaW9uCiAgdG1vZGVsIDwtIGdsbSh0ZXN0RGF0YSRvcHRfaW4gfiB0ZXN0RGF0YSRleHBGYWN0b3IsCiAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiKSAKICAjIC0gc3RvcmUgcmVzdWx0cwogIG9wdF9pbl9CTFJfUmVzdWx0cyRjYW1wYWlnbltpXSA8LSBjYW1wYWlnbnNbaV0KICBvcHRfaW5fQkxSX1Jlc3VsdHMkbl9vbGRbaV0gPC0gc3VtKHRlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJykKICBvcHRfaW5fQkxSX1Jlc3VsdHMkbl9uZXdbaV0gPC0gc3VtKHRlc3REYXRhJGV4cEZhY3RvciA9PSAnbmV3JykKICBvcHRfaW5fQkxSX1Jlc3VsdHMkY29lZmZbaV0gPC0gc3VtbWFyeSh0bW9kZWwpJGNvZWZmaWNpZW50c1syLCAxXQogIG9wdF9pbl9CTFJfUmVzdWx0cyRleHBfY29lZmZbaV0gPC0gZXhwKHN1bW1hcnkodG1vZGVsKSRjb2VmZmljaWVudHNbMiwgMV0pCiAgb3B0X2luX0JMUl9SZXN1bHRzJGNvZWZmX3pbaV0gPC0gc3VtbWFyeSh0bW9kZWwpJGNvZWZmaWNpZW50c1syLCAzXQogIG9wdF9pbl9CTFJfUmVzdWx0cyRjb2VmZl9wW2ldIDwtIHN1bW1hcnkodG1vZGVsKSRjb2VmZmljaWVudHNbMiwgNF0KICBvcHRfaW5fQkxSX1Jlc3VsdHMkc2lnW2ldIDwtIHN1bW1hcnkodG1vZGVsKSRjb2VmZmljaWVudHNbMiwgNF0gPCAuMDUKfQprbml0cjo6a2FibGUob3B0X2luX0JMUl9SZXN1bHRzLCBmb3JtYXQgPSAiaHRtbCIpICU+JSAKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgojIyAxLiBNZW1iZXJoc2lwIERhdGEgU2V0CgojIyMgMS4xIENsZWFuIHVwCgotIEV4cGVyaW1lbnRhbCBmYWN0b3I6IGluIHRoZSBtZW1iZXJzaGlwRGF0YSRrZXl3b3JkLCBhbnl0aGluZyBlbmRpbmcgaW4gIi1jdHJsIiBvciAidmFyIgotIEluIG1lbWJlcnNoaXBEYXRhJHN0YXR1czoga2VlcCBvbmx5ICIxIiBjYXNlcywgbmFtZWx5OiAiVGhlIG9ubHkgaW1wb3J0YW50IHRha2Vhd2F5IGhlcmUgaXMgdGhhdCBvbmx5IG1lbWJlcnNoaXBzIHdpdGggc3RhdHVzIDEgYXJlIGJvb2tlZCBhbmQgY29tcGxldGVkIG1lbWJlcnNoaXBzLiIgKHNlZSBSZWZlcmVuY2UgZG9jKQoKYGBge3IsIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KbWVtYmVyc2hpcERhdGEgPC0gZmlsdGVyKG1lbWJlcnNoaXBEYXRhLCBncmVwbCgiLWN0cmwkfHZhciQiLCBtZW1iZXJzaGlwRGF0YSRiYW5uZXJfa2V5d29yZCkpCm1lbWJlcnNoaXBEYXRhIDwtIGZpbHRlcihtZW1iZXJzaGlwRGF0YSwgc3RhdHVzID09IDEpCmBgYAoKIyMjIDEuIDIgUmVjb2RlIHRoZSBleHBlcmltZW50YWwgZmFjdG9yIG1lbWJlcnNoaXBEYXRhJGtleXdvcmQKCi0gU2VlOiBodHRwczovL3BoYWJyaWNhdG9yLndpa2ltZWRpYS5vcmcvVDE5NDc0NCM0MjA3NDEzCi0gTk9URTogSW4gcmVzcGVjdCB0byB0aGUgZm9sbG93aW5nIGNvbW1lbnQ6IAotICJBIHNwZWNpYWwgY2FzZSBpcyB0aGUgbGFzdCBjYW1wYWlnbiBvbiB3aWtpcGVkaWEuZGUgKCJ3cGRlIikuIEl0IGZlYXR1cmVkIGZvdXIgYmFubmVycyBhbmQgaGFzIHRoZXJlZm9yZSBmb3VyIGtleXdvcmRzLkV2ZXJ5IHVzZXIgaW4gdGhlIGNhbXBhaWduIHNhdyBvbmUgbGFyZ2UgYmFubmVyICgiZnVsbHRvcCIiKSBhbmQgYWZ0ZXIgdGhhdCBvbmx5IHNtYWxsIGJhbm5lcnMgKCJ0b3AiIikuIFNvIG9uZSBjYW4gYWdncmVnYXRlIHRoZSBkYXRhIG9mIHRoZSBsYXJnZSBhbmQgc21hbGwgYmFubmVycyBvciB0cmVhdCBpdCBhcyB0d28gdGVzdCBjYW1wYWlnbnMuIgotIFRoZSBmb3VyIHBhaXJlZCBiYW5uZXJzIGZvciB0aGlzIGNhbXBhaWduIHdlcmUgdHJlYXRlZCBhcyB0d28gYmFubmVycyBvbmx5IChvbGQvbmV3KTsKLSByYXRpbzogZXZlcnkgdXNlciBuZWNlc3NhcmlseSBzYXcgYSBwYWlyIG9mIGJhbm5lcnM7IG1ha2VzIG5vIHNlbnNlIHRyZWF0aW5nIHRoZW0gYXMgZGlmZmVyZW50IGZhY3RvcnMuCgpgYGB7ciwgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpyZWNvZGluZ1NjaGVtZSA8LSByZWFkLmNzdignZGF0YS9yZWNvZGluZ1NjaGVtZS5jc3YnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRikKb2xkQ29kZXMgPC0gdW5pcXVlKHJlY29kaW5nU2NoZW1lJGBrZXl3b3JkIG9mIG9sZCBscGApCm5ld0NvZGVzIDwtIHVuaXF1ZShyZWNvZGluZ1NjaGVtZSRga2V5d29yZCBvZiBuZXcgbHBgKQptZW1iZXJzaGlwRGF0YSRleHBGYWN0b3IgPC0gc2FwcGx5KG1lbWJlcnNoaXBEYXRhJGJhbm5lcl9rZXl3b3JkLCBmdW5jdGlvbih4KSB7CiAgaWYgKHggJWluJSBvbGRDb2RlcykgewogICAgcmV0dXJuKCJvbGQiKQogIH0gZWxzZSBpZiAoeCAlaW4lIG5ld0NvZGVzKSB7CiAgICByZXR1cm4oIm5ldyIpCiAgfSBlbHNlIHsKICAgIHJldHVybigiVW5uYXNpZ25lZCIpCiAgfQp9KQojIC0gZmlsdGVyIG91dCAnVW5uYXNpZ25lZCcgY29kZXM6Cm1lbWJlcnNoaXBEYXRhIDwtIGZpbHRlcihtZW1iZXJzaGlwRGF0YSwgIShleHBGYWN0b3IgPT0gJ1VubmFzaWduZWQnKSkKYGBgCgojIyMgMS4gMyBSZXNjYWxlIG1lbWJlcnNoaXBfZmVlIHRvIHllYXJseSBkYXRhIGJ5IGludGVydmFsCgotIFRoZSB5ZWFybHkgZmVlIGNhbiBiZSBwYWlkIG9uY2UgYSB5ZWFyIG9yIGV2ZXJ5IFggbW9udGg7IHRoZSBudW1iZXIgaW4gdGhlIGRhdGEgZmllbGQgc2lnbmlmaWVzIDEJPSBib29raW5nIGV2ZXJ5IG1vbnRoICwgMwk9IGJvb2tpbmcgZXZlcnkgdGhyZWUgbW9udGhzLCA2CT0gYm9va2luZyBldmVyeSBzaXggbW9udGhzLCAxMgk9IGJvb2tpbmcgZXZlcnkgdHdlbHZlIG1vbnRocwotIE5PVEU6ICIwIiB2YWx1ZSBmb3IgdGhlIGRvbmF0aW9uc0RhdGEkaW50ZXJ2YWwgd291bGQgbWVhbjogb25jZSBwZXIgeWVhcgoKYGBge3IsIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KbWVtYmVyc2hpcERhdGEkYW1vdW50WWVhcmx5IDwtIG51bWVyaWMoZGltKG1lbWJlcnNoaXBEYXRhKVsxXSkKbWVtYmVyc2hpcERhdGEkYW1vdW50WWVhcmx5W21lbWJlcnNoaXBEYXRhJG1lbWJlcnNoaXBfZmVlX2ludGVydmFsID09IDAgfCBtZW1iZXJzaGlwRGF0YSRtZW1iZXJzaGlwX2ZlZV9pbnRlcnZhbCA9PSAxMl0gPC0gCiAgbWVtYmVyc2hpcERhdGEkbWVtYmVyc2hpcF9mZWVbbWVtYmVyc2hpcERhdGEkbWVtYmVyc2hpcF9mZWVfaW50ZXJ2YWwgPT0gMCB8IG1lbWJlcnNoaXBEYXRhJG1lbWJlcnNoaXBfZmVlX2ludGVydmFsID09IDEyXQptZW1iZXJzaGlwRGF0YSRhbW91bnRZZWFybHlbbWVtYmVyc2hpcERhdGEkbWVtYmVyc2hpcF9mZWVfaW50ZXJ2YWwgPT0gMV0gPC0gCiAgbWVtYmVyc2hpcERhdGEkbWVtYmVyc2hpcF9mZWVbbWVtYmVyc2hpcERhdGEkbWVtYmVyc2hpcF9mZWVfaW50ZXJ2YWwgPT0gMV0gKiAxMgptZW1iZXJzaGlwRGF0YSRhbW91bnRZZWFybHlbbWVtYmVyc2hpcERhdGEkbWVtYmVyc2hpcF9mZWVfaW50ZXJ2YWwgPT0gM10gPC0gCiAgbWVtYmVyc2hpcERhdGEkbWVtYmVyc2hpcF9mZWVbbWVtYmVyc2hpcERhdGEkbWVtYmVyc2hpcF9mZWVfaW50ZXJ2YWwgPT0gM10gKiA0Cm1lbWJlcnNoaXBEYXRhJGFtb3VudFllYXJseVttZW1iZXJzaGlwRGF0YSRtZW1iZXJzaGlwX2ZlZV9pbnRlcnZhbCA9PSA2XSA8LSAKICBtZW1iZXJzaGlwRGF0YSRtZW1iZXJzaGlwX2ZlZVttZW1iZXJzaGlwRGF0YSRtZW1iZXJzaGlwX2ZlZV9pbnRlcnZhbCA9PSA2XSAqIDIKYGBgCgojIyMgMS4gNCBTZWxlY3Qgb25seSByZWxldmFudCB2YXJpYWJsZXMKCmBgYHtyLCBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9Cm1lbWJlcnNoaXBEYXRhIDwtIHNlbGVjdChtZW1iZXJzaGlwRGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgIGJhbm5lcl9jYW1wYWlnbiwgZXhwRmFjdG9yLCBhbW91bnRZZWFybHksIGRvbmF0aW9uX3JlY2VpcHQpCmBgYAoKIyMjIDEuIDUgQS9CIHRlc3RzOiBtZW1iZXJzaGlwX2ZlZQoKIyMjIyBPdXRsaW5lCgpUaGUgYW5hbHlzaXMgb2YgdGhlIE1lbWJlcnNoaXAgZGF0YSBzZXQgcmVsaWVzIG9uIG5vbi1wYXJhbWV0cmljIHRlc3RzIG9ubHkgKE1hbm4tV2hpdG5leSBVIHRlc3QpLCBiZWNhdXNlIHdlIGdldCB0byBoYXZlIHJlYWxseSBzbWFsbCBzYW1wbGUgc2l6ZXMgcGVyIGNhbXBhaWduIGZvbGxvd2luZyB0aGUgZGF0YSBjbGVhbi11cC4KKipDb25jbHVzaW9uLioqIE5vIGVmZmVjdCBvZiB0aGUgbmV3IHBhZ2UgaW4gYW55IG9mIHRoZSBjYW1wYWlnbnMuCgoKIyMjIyBNZXRob2QgRDEuIE5vbi1wYXJhbWV0cmljIHRlc3RzCgoqKlJlc3VsdHMuKiogYGNhbXBhaWduYCBzdGFuZHMgZm9yIHRoZSBjYW1wYWlnbiBjb2RlLCBgbl9vbGRgIGFuZCBgbl9uZXdgIGFyZSB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBmb3IgdGhlICJuZXciIGFuZCB0aGUgIm9sZCIgcGFnZSByZXNwZWN0aXZlbHksIGBXYCBpcyB0aGUgV2lsY294b24gdGVzdCBzdGF0aXN0aWNzLCBhbmQgYE1XVVRlc3RfcGAgdGhlIGFzc29jaWF0ZWQgcHJvYmFiaWxpdHkgb2YgdGhlIFR5cGUgSSBFcnJvci4gCgpgYGB7ciwgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcGFpZ25zIDwtIHVuaXF1ZShtZW1iZXJzaGlwRGF0YSRiYW5uZXJfY2FtcGFpZ24pCm1ldGhvZEQxUmVzdWx0c19tZW1iZXJoc2lwIDwtIGRhdGEuZnJhbWUoCiAgY2FtcGFpZ24gPSBjaGFyYWN0ZXIobGVuZ3RoKGNhbXBhaWducykpLAogIG5fb2xkID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgbl9uZXcgPSBudW1lcmljKGxlbmd0aChjYW1wYWlnbnMpKSwKICBXID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgTVdVVGVzdF9wID0gbnVtZXJpYyhsZW5ndGgoY2FtcGFpZ25zKSksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEYKKQpmb3IgKGkgaW4gMTpsZW5ndGgoY2FtcGFpZ25zKSkgewogICMgLSBzZWxlY3QgdGVzdERhdGEKICB0ZXN0RGF0YSA8LSBtZW1iZXJzaGlwRGF0YSAlPiUgCiAgICBmaWx0ZXIoYmFubmVyX2NhbXBhaWduICVpbiUgY2FtcGFpZ25zW2ldKSAlPiUgCiAgICBzZWxlY3QoZXhwRmFjdG9yLCBhbW91bnRZZWFybHkpCiAgdGVzdERhdGEkZXhwRmFjdG9yIDwtIGZhY3Rvcih0ZXN0RGF0YSRleHBGYWN0b3IsIGxldmVscyA9IGMoJ25ldycsICdvbGQnKSkKICAjIC0gbm9uLXBhcmFtZXRyaWMgdGVzdAogIG13dSA8LSB3aWxjb3gudGVzdCh0ZXN0RGF0YSRhbW91bnRZZWFybHkgfiB0ZXN0RGF0YSRleHBGYWN0b3IpIAogICMgLSBzdG9yZSByZXN1bHRzCiAgbWV0aG9kRDFSZXN1bHRzX21lbWJlcmhzaXAkY2FtcGFpZ25baV0gPC0gY2FtcGFpZ25zW2ldCiAgbWV0aG9kRDFSZXN1bHRzX21lbWJlcmhzaXAkbl9vbGRbaV0gPC0gc3VtKHRlc3REYXRhJGV4cEZhY3RvciA9PSAnb2xkJykKICBtZXRob2REMVJlc3VsdHNfbWVtYmVyaHNpcCRuX25ld1tpXSA8LSBzdW0odGVzdERhdGEkZXhwRmFjdG9yID09ICduZXcnKQogIG1ldGhvZEQxUmVzdWx0c19tZW1iZXJoc2lwJFdbaV0gPC0gbXd1JHN0YXRpc3RpYwogIG1ldGhvZEQxUmVzdWx0c19tZW1iZXJoc2lwJE1XVVRlc3RfcFtpXSA8LSBtd3UkcC52YWx1ZQp9CmtuaXRyOjprYWJsZShtZXRob2REMVJlc3VsdHNfbWVtYmVyaHNpcCwgZm9ybWF0ID0gImh0bWwiKSAlPiUgCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKCgoKCgoKCgoKCgoKCg==