1 Loading packages and creating in the data set

We’ll start by loading the tidyverse:

Since the variance of the slope, \(b_1\), depends on \(S_{XX}\):

\[S_{XX} = \sum_{i=1}^n(X_i-\bar{X})^2\]

We need to keep the \(X_i\) the same throughout the simulation for each iteration. We’ll keep it simple and let \(x_i = 2i\) with \(i = 1, ..., 10\)

X <- 2*(1:10)

X
##  [1]  2  4  6  8 10 12 14 16 18 20

The \(S_{XX}\) is:

# S_XX = sum (X_i - X_bar)^2
S_XX <- sum((X - mean(X))^2)
S_XX
## [1] 330

When performing a simulation, we need to create a model to generate our data. The population model for our simulation is:

\[Y_i = 20 + 40X_i + \varepsilon_i \\ \varepsilon_i \sim N(0, 660)\]

For each simulation we run, we’ll generate \(Y_i\) using the model above

2 Theoretical distribution for \(b_1\)

According to the slides we saw in class, as long as

\[Y_i \sim N(\beta_0 + \beta_1 X_i, \sigma^2)\]

are are independent, then the distribution of \(b_1\) is:

\[b_1 \sim N\left(\beta_1, \frac{\sigma^2}{S_{XX}}\right)\]

Since we control the population, we know the distribution \(b_1\) for our example should follow:

\[b_1 \sim N\left(40, \frac{660}{330}\right)\]

3 Simulating 10,000 samples of n = 10

We’ll generate 10,000 samples of 10 \(Y_i\) values each based on the model stated above, which is theoretically akin to taking a random sample from a population.

From each sample, we’ll save the following info:

  1. The sample
  2. \(b_0\)
  3. \(b_1\)
  4. \(SSE\)
# Simulation and sample sizes
m <- 1e5
sample_size <- 10

# Setting the parameters of the model
beta0 <- 20; beta1 <- 40; sigma2_e <- 660

# Data frame to save the result:
sim_df <- 
  data.frame(
    sample = 1:m,
    b0 = -100,
    b1 = -100,
    SSE = -1
  )

Before we conduct the full simulation, let’s generate one sample and calculate the stats needed as an example:

### Let's generate a random sample for sake of showing how:
set.seed(4210)
# The response from the model:
Y_loop <- beta0 + beta1 * X + 
          # Random error term
          rnorm(sample_size, mean = 0, sd = sqrt(sigma2_e))

# Find b1:
b1_loop <- 
  # S_XY
  sum((X - mean(X))*(Y_loop - mean(Y_loop))) / S_XX

# Finding b0:
b0_loop <- mean(Y_loop) - b1_loop * mean(X)

# Calculating y_hat
Y_hat_loop <- b0_loop + b1_loop * X

# Residuals:
res_loop <- Y_loop - Y_hat_loop

# SSE:
SSE_loop <- sum(res_loop^2)

# Now we'll save the results in the first row
sim_df[1, 2:ncol(sim_df)] <- c(b0_loop, b1_loop, SSE_loop)


tibble(sim_df)
## # A tibble: 100,000 × 4
##    sample     b0     b1   SSE
##     <int>  <dbl>  <dbl> <dbl>
##  1      1   23.5   39.8 1657.
##  2      2 -100   -100     -1 
##  3      3 -100   -100     -1 
##  4      4 -100   -100     -1 
##  5      5 -100   -100     -1 
##  6      6 -100   -100     -1 
##  7      7 -100   -100     -1 
##  8      8 -100   -100     -1 
##  9      9 -100   -100     -1 
## 10     10 -100   -100     -1 
## # ℹ 99,990 more rows

Now we’ll add the remaining sample stats to the data frame:

# Performing the simulation:
for (i in 2:m){
  # The response from the model:
  Y_loop <- beta0 + beta1 * X + 
            # Random error term
            rnorm(sample_size, mean = 0, sd = sqrt(sigma2_e))
  
  # Find b1:
  b1_loop <- sum((X - mean(X))*(Y_loop - mean(Y_loop))) / S_XX
  
  # Finding b0:
  b0_loop <- mean(Y_loop) - b1_loop * mean(X)
  
  # Calculating y_hat
  Y_hat_loop <- b0_loop + b1_loop * X
  
  # Residuals:
  res_loop <- Y_loop - Y_hat_loop
  
  # SSE:
  SSE_loop <- sum(res_loop^2)
  
  # Now we'll save the results in the first row
  sim_df[i, 2:ncol(sim_df)] <- c(b0_loop, b1_loop, SSE_loop)
}

tibble(sim_df)
## # A tibble: 100,000 × 4
##    sample    b0    b1   SSE
##     <int> <dbl> <dbl> <dbl>
##  1      1 23.5   39.8 1657.
##  2      2 16.1   41.0 3318.
##  3      3 28.9   38.7 5290.
##  4      4 57.0   37.9 2793.
##  5      5 24.6   40.2 2391.
##  6      6 19.8   39.6 4262.
##  7      7 13.7   40.4 2323.
##  8      8 40.0   38.4 3262.
##  9      9 10.3   40.1 3662.
## 10     10 -6.51  41.2 4166.
## # ℹ 99,990 more rows

4 Distribution of \(b_1\)

Let’s create a histogram of our simulated slopes and superimpose a Normal curve on it.

Reminder: If the theory is correct, then:

\[b_1 \sim N\left(40, \frac{660}{330}\right)\]

ggplot(
  data = sim_df,
  mapping = aes(
    x = b1
  )
) + 
  geom_histogram(
    # Need to swap from count to density on the y-axis to place a normal curve
    mapping = aes(y = after_stat(density)),
    bins = 40,
    fill = 'steelblue',
    color = 'white'
  ) + 
  # Adding the Normal curve:
  stat_function(
    fun = dnorm,
    args = list(mean = beta1, sd = sqrt(sigma2_e/S_XX)),
    color = 'red',
    linewidth = 1,
    alpha = 0.5
  ) + 
  theme_bw()  +
  labs(
    title = 'Histogram of simulated slopes'
  ) + 
  scale_y_continuous(
    expand = c(0, 0, 0.05, 0)
  )

Histogram looks pretty accurate!

What about the test statistics and p-values? Do they ‘line up’ with what the theory says?

Result: If \(H_0\) is true, then the p-value is a random variable that follows a uniform distribution between 0 and 1:

\[\text{p-val} | H_0 \sim \text{Uniform}(0, 1)\]

4.1 Type I error rate: \(\sigma^2\) known

So if we calculate the test statistic and p-value for all of our simulated samples, then 5% of our samples should give us a p-value below 0.05 (or whatever significance level you choose)

\[P(\text{p-val} < \alpha) = \alpha\]

# Finding the p-values:
sim_df <- 
  sim_df |>
  mutate(
    # Test statistic
    test_stat = (b1 - beta1) / sqrt(sigma2_e/S_XX),
    # Now p-value for Beta1 =\= 40
    p_val_sigma = 2*pnorm(abs(test_stat), lower.tail = F)
  )

sim_df |>
  dplyr::select(sample, b1, test_stat, p_val_sigma) |>
  tibble()
## # A tibble: 100,000 × 4
##    sample    b1 test_stat p_val_sigma
##     <int> <dbl>     <dbl>       <dbl>
##  1      1  39.8   -0.138        0.890
##  2      2  41.0    0.693        0.488
##  3      3  38.7   -0.937        0.349
##  4      4  37.9   -1.50         0.134
##  5      5  40.2    0.164        0.870
##  6      6  39.6   -0.259        0.795
##  7      7  40.4    0.255        0.799
##  8      8  38.4   -1.17         0.244
##  9      9  40.1    0.0506       0.960
## 10     10  41.2    0.849        0.396
## # ℹ 99,990 more rows

Let’s plot them and calculate the probability of rejecting a true \(H_0\):

# Calculating the probability of a type 1 error
prob_type1_alpha_5 <- mean(sim_df$p_val_sigma < 0.05)

# How wide to make each bin
binwidth = 0.01

ggplot(
  data = sim_df,
  mapping = aes(
    x = p_val_sigma,
    fill = if_else(p_val_sigma < 0.05, 'tomato', 'steelblue')
  )
) + 
  geom_histogram(
    # Need to swap from count to density on the y-axis to place a normal curve
    mapping = aes(y = after_stat(count) / (m * binwidth)),
    breaks = seq(0, 1, binwidth),
    #fill = 'steelblue',
    color = 'white'
  ) + 
  # Adding the Uniform 'curve':
  stat_function(
    fun = dunif,
    args = list(min = 0, max = 1),
    color = 'red',
    linewidth = 1,
    alpha = 0.5
  ) + 
  # Adding the probability of Type 1
  labs(
    title = 'Distribution of p-values when Ho is true and sigma is known',
    subtitle = paste('When alpha = 5% the chance of Type I error is', 
                     scales::percent(prob_type1_alpha_5)),
    caption = paste('Results from a simulation of', scales::comma(m), 
                    'simulations each with a sample size of', sample_size),
    x = 'P-value',
    y = 'Density'
  ) +
  theme_bw()  +
  scale_fill_identity() + 
  scale_y_continuous(
    expand = c(0, 0, 0.05, 0)
  )

The distribution above assumes we know what \(\sigma^2\) is, which we usually don’t. What happens if we swap \(\sigma^2\) with the MSE?

4.2 Type I error rate: \(\sigma^2\) unknown

Instead of using the standard deviation of \(b_1\), we’ll have to use the standard error:

\[SE(b_1) = \sqrt{\frac{MSE}{S_{XX}}}\]

We can calculate the \(MSE\) for each sample by:

\[MSE = \frac{SSE}{n-2}\]

So let’s find the Standard error, test stat, and p-value using the \(MSE\):

\[z = \frac{b_1 - \beta_{1,0}}{\sqrt{\frac{MSE}{S_{XX}}}}\]

# Finding the p-values:
sim_df <- 
  sim_df |>
  mutate(
    MSE = SSE / (sample_size - 2),
    # Test statistic using mse
    test_stat_mse = (b1 - beta1) / sqrt(MSE/S_XX),
    # Now p-value for Beta1 =\= 40
    p_val_mse = 2*pnorm(abs(test_stat_mse), lower.tail = F)
  )

sim_df |>
  dplyr::select(sample, b1, MSE, test_stat_mse, p_val_mse) |>
  tibble()
## # A tibble: 100,000 × 5
##    sample    b1   MSE test_stat_mse p_val_mse
##     <int> <dbl> <dbl>         <dbl>     <dbl>
##  1      1  39.8  207.       -0.246     0.806 
##  2      2  41.0  415.        0.874     0.382 
##  3      3  38.7  661.       -0.936     0.349 
##  4      4  37.9  349.       -2.06      0.0396
##  5      5  40.2  299.        0.244     0.807 
##  6      6  39.6  533.       -0.289     0.773 
##  7      7  40.4  290.        0.384     0.701 
##  8      8  38.4  408.       -1.48      0.138 
##  9      9  40.1  458.        0.0607    0.952 
## 10     10  41.2  521.        0.956     0.339 
## # ℹ 99,990 more rows

4.2.1 Plot of the test statistic using MSE instead of \(\sigma^2\)

The histogram of the test statistic is shown below. Does it look like the Standard Normal distribution fit the histogram?

gg_test_stat_mse_hist <- 
  ggplot(
    # There are a few extreme values, so we'll only look at -5 < z < 5
    data = sim_df |>
      filter(between(test_stat_mse, -5, 5)),
    mapping = aes(
      x = test_stat_mse
    )
  ) + 
  geom_histogram(
    # Need to swap from count to density on the y-axis to place a normal curve
    mapping = aes(y = after_stat(density)),
    bins = 40,
    fill = 'steelblue',
    color = 'white'
  ) + 
  theme_bw()  +
  labs(
    title = paste('Histogram of', scales::comma(m), 'test statistics for the slope'),
    subtitle = 'Calculated using the MSE',
    x = 'Test statistics'
  ) + 
  scale_y_continuous(
    expand = c(0, 0, 0.05, 0)
  ) + 
  scale_x_continuous(
    breaks = -5:5
  )


gg_test_stat_mse_hist + 
  # Adding the Normal curve:
  stat_function(
    fun = dnorm,
    args = list(mean = 0, sd = 1),
    color = 'red',
    linewidth = 1,
    alpha = 0.5
  ) + 
  labs(
    caption = 'Standard Normal Distribution Overlayed on the Histogram'
  )

The Normal curve looks close, but the middle bars are consistently below the line while the tails are above the line. That typically indicates that a wider distribution is needed instead of a Normal distribution.

Let’s check the p-values as well!

4.2.2 Plot of the p-values using MSE and Normal distribution

Again, plotting them and calculate the probability of rejecting a true \(H_0\) assuming the test statistic follows a Standard Normal:

# Calculating the probability of a type 1 error
prob_type1_alpha_5_mse <- mean(sim_df$p_val_mse < 0.05)

# Creating the graph
ggplot(
  data = sim_df,
  mapping = aes(
    x = p_val_mse,
    fill = if_else(p_val_mse < 0.05, 'tomato', 'steelblue'),
   # group = 1
  )
) + 
  geom_histogram(
    # Need to swap from count to density on the y-axis to place a normal curve
    mapping = aes(y = after_stat(count) / (m * binwidth)),
    breaks = seq(0, 1, binwidth),
    color = 'white'
  ) + 
  # Adding the Uniform 'curve':
  stat_function(
    fun = dunif,
    args = list(min = 0, max = 1),
    color = 'red',
    linewidth = 1,
    alpha = 0.5
  ) + 
  # Adding the probability of Type 1
  labs(
    title = 'Distribution of p-values when H0 is true, using a Normal with sigma unknown',
    subtitle = paste('alpha = 5% and Type I probability is', 
                     scales::percent(prob_type1_alpha_5_mse)),
    caption = paste('Results from a simulation of', scales::comma(m), 
                    'simulations each with a sample size of', sample_size),
    x = 'P-value',
    y = 'Density'
  ) +
  theme_bw()  +
  scale_fill_identity() + 
  scale_y_continuous(
    expand = c(0, 0, 0.05, 0)
  )

Our chance of a Type I error is almost twice the significance level! Why?

4.3 P-value using a t-distribution

Like what you did waaaay back in your introductory stats class, when you replace \(\sigma^2\) with its sample statistic equivalent, the test statistic is no longer Normally distributed.

Instead, the test stat will follow a **t-distribution* with degrees of freedom equal to the denominator of the formula to calculate \(\hat{\sigma}^2\):

\[\hat{\sigma}^2 = \sqrt{\frac{SSE}{n - 2}}\]

So if we use \(MSE\) in place of \(\sigma^2\), then the test statistic will follow a t-distribution with \(n - 2\) degrees of freedom!

4.3.1 Histogram of the test statistic with a t-distribution

gg_test_stat_mse_hist + 
  # Adding the Normal curve:
  stat_function(
    fun = dt,
    args = list(df = sample_size - 2),
    color = 'red',
    linewidth = 1,
    alpha = 0.5
  ) + 
  labs(
    caption = paste('t-distribution with', sample_size - 2, 
                    'df overlayed on the histogram')
  )

The t-distribution fits great!

4.3.2 Histogram of p-values using MSE and t-distribution

The code chunk below will calculate the p-value using the t-distribution with df = 8:

# Finding the p-values:
sim_df <- 
  sim_df |>
  mutate(
    # Now p-value for Beta1 =\= 40 using a t-distribution
    t_p_val = 2*pt(abs(test_stat_mse), df = sample_size - 2, lower.tail = F)
  )

sim_df |>
  dplyr::select(sample, b1, MSE, test_stat_mse, t_p_val) |>
  tibble()
## # A tibble: 100,000 × 5
##    sample    b1   MSE test_stat_mse t_p_val
##     <int> <dbl> <dbl>         <dbl>   <dbl>
##  1      1  39.8  207.       -0.246   0.812 
##  2      2  41.0  415.        0.874   0.407 
##  3      3  38.7  661.       -0.936   0.377 
##  4      4  37.9  349.       -2.06    0.0735
##  5      5  40.2  299.        0.244   0.813 
##  6      6  39.6  533.       -0.289   0.780 
##  7      7  40.4  290.        0.384   0.711 
##  8      8  38.4  408.       -1.48    0.176 
##  9      9  40.1  458.        0.0607  0.953 
## 10     10  41.2  521.        0.956   0.367 
## # ℹ 99,990 more rows

Again, plotting the p-values calculated above and calculate the probability of rejecting a true \(H_0\) assuming the test statistic follows a t-distribution:

# Calculating the probability of a type 1 error
prob_type1_alpha_5_t <- mean(sim_df$t_p_val < 0.05)

# Graph
ggplot(
  data = sim_df,
  mapping = aes(
    x = t_p_val,
    fill = if_else(t_p_val < 0.05, 'tomato', 'steelblue'),
   # group = 1
  )
) + 
  geom_histogram(
    # Need to swap from count to density on the y-axis to place a normal curve
    mapping = aes(y = after_stat(count) / (m * binwidth)),
    breaks = seq(0, 1, binwidth),
    color = 'white'
  ) + 
  # Adding the Uniform 'curve':
  stat_function(
    fun = dunif,
    args = list(min = 0, max = 1),
    color = 'red',
    linewidth = 1,
    alpha = 0.5
  ) + 
  # Adding the probability of Type 1
  labs(
    title = 'Distribution of p-values when Ho is true, using MSE, and t-distribution',
    subtitle = paste('alpha = 5% and Type I probability is', 
                     scales::percent(prob_type1_alpha_5_t)),
    caption = paste('Results from a simulation of', scales::comma(m), 
                    'simulations each with a sample size of', sample_size),
    x = 'P-value',
    y = 'Density'
  ) +
  theme_bw()  +
  scale_fill_identity() + 
  scale_y_continuous(
    expand = c(0, 0, 0.05, 0)
  )

Now it matches!

LS0tDQp0aXRsZTogIkRpc3RyaWJ1dGlvbiBvZiB0aGUgc2FtcGxlIHNsb3BlIg0KYXV0aG9yOiAiQ2hhcHRlciAyIg0KZGF0ZTogIlNUQSA0MjEwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGZpZ193aWR0aDogOA0KICAgIGZpZ19oZWlnaHQ6IDYNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIyBMb2FkaW5nIHBhY2thZ2VzIGFuZCBjcmVhdGluZyBpbiB0aGUgZGF0YSBzZXQNCg0KV2UnbGwgc3RhcnQgYnkgbG9hZGluZyB0aGUgYHRpZHl2ZXJzZWA6DQoNCmBgYHtyIHBhY2thZ2UsIGluY2x1ZGU9RiwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIFBhY2thZ2VzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQpTaW5jZSB0aGUgdmFyaWFuY2Ugb2YgdGhlIHNsb3BlLCAkYl8xJCwgZGVwZW5kcyBvbiAkU197WFh9JDoNCg0KJCRTX3tYWH0gPSBcc3VtX3tpPTF9Xm4oWF9pLVxiYXJ7WH0pXjIkJA0KDQpXZSBuZWVkIHRvIGtlZXAgdGhlICRYX2kkIHRoZSBzYW1lIHRocm91Z2hvdXQgdGhlIHNpbXVsYXRpb24gZm9yIGVhY2ggaXRlcmF0aW9uLiBXZSdsbCBrZWVwIGl0IHNpbXBsZSBhbmQgbGV0ICR4X2kgPSAyaSQgd2l0aCAkaSA9IDEsIC4uLiwgMTAkDQoNCmBgYHtyIHBvcF9kZn0NClggPC0gMiooMToxMCkNCg0KWA0KYGBgDQoNClRoZSAkU197WFh9JCBpczoNCg0KYGBge3IgU19YWH0NCiMgU19YWCA9IHN1bSAoWF9pIC0gWF9iYXIpXjINClNfWFggPC0gc3VtKChYIC0gbWVhbihYKSleMikNClNfWFgNCmBgYA0KDQpXaGVuIHBlcmZvcm1pbmcgYSBzaW11bGF0aW9uLCB3ZSBuZWVkIHRvIGNyZWF0ZSBhIG1vZGVsIHRvIGdlbmVyYXRlIG91ciBkYXRhLiBUaGUgcG9wdWxhdGlvbiBtb2RlbCBmb3Igb3VyIHNpbXVsYXRpb24gaXM6DQoNCiQkWV9pID0gMjAgKyA0MFhfaSArIFx2YXJlcHNpbG9uX2kgXFwgXHZhcmVwc2lsb25faSBcc2ltIE4oMCwgNjYwKSQkDQoNCkZvciBlYWNoIHNpbXVsYXRpb24gd2UgcnVuLCB3ZSdsbCBnZW5lcmF0ZSAkWV9pJCB1c2luZyB0aGUgbW9kZWwgYWJvdmUNCg0KIyMgVGhlb3JldGljYWwgZGlzdHJpYnV0aW9uIGZvciAkYl8xJA0KDQpBY2NvcmRpbmcgdG8gdGhlIHNsaWRlcyB3ZSBzYXcgaW4gY2xhc3MsIGFzIGxvbmcgYXMNCg0KJCRZX2kgXHNpbSBOKFxiZXRhXzAgKyBcYmV0YV8xIFhfaSwgXHNpZ21hXjIpJCQNCg0KYXJlIGFyZSBpbmRlcGVuZGVudCwgdGhlbiB0aGUgZGlzdHJpYnV0aW9uIG9mICRiXzEkIGlzOg0KDQokJGJfMSBcc2ltIE5cbGVmdChcYmV0YV8xLCBcZnJhY3tcc2lnbWFeMn17U197WFh9fVxyaWdodCkkJA0KDQpTaW5jZSB3ZSBjb250cm9sIHRoZSBwb3B1bGF0aW9uLCB3ZSBrbm93IHRoZSBkaXN0cmlidXRpb24gJGJfMSQgZm9yIG91ciBleGFtcGxlIHNob3VsZCBmb2xsb3c6DQoNCiQkYl8xIFxzaW0gTlxsZWZ0KDQwLCBcZnJhY3s2NjB9ezMzMH1ccmlnaHQpJCQNCg0KIyMgU2ltdWxhdGluZyAxMCwwMDAgc2FtcGxlcyBvZiBuID0gMTANCg0KV2UnbGwgZ2VuZXJhdGUgMTAsMDAwIHNhbXBsZXMgb2YgMTAgJFlfaSQgdmFsdWVzIGVhY2ggYmFzZWQgb24gdGhlIG1vZGVsIHN0YXRlZCBhYm92ZSwgd2hpY2ggaXMgdGhlb3JldGljYWxseSBha2luIHRvIHRha2luZyBhIHJhbmRvbSBzYW1wbGUgZnJvbSBhIHBvcHVsYXRpb24uDQoNCkZyb20gZWFjaCBzYW1wbGUsIHdlJ2xsIHNhdmUgdGhlIGZvbGxvd2luZyBpbmZvOg0KDQoxKSAgVGhlIHNhbXBsZQ0KMikgICRiXzAkDQozKSAgJGJfMSQNCjQpICAkU1NFJA0KDQpgYGB7ciBzaW1fZGZ9DQojIFNpbXVsYXRpb24gYW5kIHNhbXBsZSBzaXplcw0KbSA8LSAxZTUNCnNhbXBsZV9zaXplIDwtIDEwDQoNCiMgU2V0dGluZyB0aGUgcGFyYW1ldGVycyBvZiB0aGUgbW9kZWwNCmJldGEwIDwtIDIwOyBiZXRhMSA8LSA0MDsgc2lnbWEyX2UgPC0gNjYwDQoNCiMgRGF0YSBmcmFtZSB0byBzYXZlIHRoZSByZXN1bHQ6DQpzaW1fZGYgPC0gDQogIGRhdGEuZnJhbWUoDQogICAgc2FtcGxlID0gMTptLA0KICAgIGIwID0gLTEwMCwNCiAgICBiMSA9IC0xMDAsDQogICAgU1NFID0gLTENCiAgKQ0KYGBgDQoNCkJlZm9yZSB3ZSBjb25kdWN0IHRoZSBmdWxsIHNpbXVsYXRpb24sIGxldCdzIGdlbmVyYXRlIG9uZSBzYW1wbGUgYW5kIGNhbGN1bGF0ZSB0aGUgc3RhdHMgbmVlZGVkIGFzIGFuIGV4YW1wbGU6DQoNCmBgYHtyIHNhbXBsZV8xfQ0KIyMjIExldCdzIGdlbmVyYXRlIGEgcmFuZG9tIHNhbXBsZSBmb3Igc2FrZSBvZiBzaG93aW5nIGhvdzoNCnNldC5zZWVkKDQyMTApDQojIFRoZSByZXNwb25zZSBmcm9tIHRoZSBtb2RlbDoNCllfbG9vcCA8LSBiZXRhMCArIGJldGExICogWCArIA0KICAgICAgICAgICMgUmFuZG9tIGVycm9yIHRlcm0NCiAgICAgICAgICBybm9ybShzYW1wbGVfc2l6ZSwgbWVhbiA9IDAsIHNkID0gc3FydChzaWdtYTJfZSkpDQoNCiMgRmluZCBiMToNCmIxX2xvb3AgPC0gDQogICMgU19YWQ0KICBzdW0oKFggLSBtZWFuKFgpKSooWV9sb29wIC0gbWVhbihZX2xvb3ApKSkgLyBTX1hYDQoNCiMgRmluZGluZyBiMDoNCmIwX2xvb3AgPC0gbWVhbihZX2xvb3ApIC0gYjFfbG9vcCAqIG1lYW4oWCkNCg0KIyBDYWxjdWxhdGluZyB5X2hhdA0KWV9oYXRfbG9vcCA8LSBiMF9sb29wICsgYjFfbG9vcCAqIFgNCg0KIyBSZXNpZHVhbHM6DQpyZXNfbG9vcCA8LSBZX2xvb3AgLSBZX2hhdF9sb29wDQoNCiMgU1NFOg0KU1NFX2xvb3AgPC0gc3VtKHJlc19sb29wXjIpDQoNCiMgTm93IHdlJ2xsIHNhdmUgdGhlIHJlc3VsdHMgaW4gdGhlIGZpcnN0IHJvdw0Kc2ltX2RmWzEsIDI6bmNvbChzaW1fZGYpXSA8LSBjKGIwX2xvb3AsIGIxX2xvb3AsIFNTRV9sb29wKQ0KDQoNCnRpYmJsZShzaW1fZGYpDQpgYGANCg0KTm93IHdlJ2xsIGFkZCB0aGUgcmVtYWluaW5nIHNhbXBsZSBzdGF0cyB0byB0aGUgZGF0YSBmcmFtZToNCg0KYGBge3IgYWRkX3NhbXBsZXN9DQojIFBlcmZvcm1pbmcgdGhlIHNpbXVsYXRpb246DQpmb3IgKGkgaW4gMjptKXsNCiAgIyBUaGUgcmVzcG9uc2UgZnJvbSB0aGUgbW9kZWw6DQogIFlfbG9vcCA8LSBiZXRhMCArIGJldGExICogWCArIA0KICAgICAgICAgICAgIyBSYW5kb20gZXJyb3IgdGVybQ0KICAgICAgICAgICAgcm5vcm0oc2FtcGxlX3NpemUsIG1lYW4gPSAwLCBzZCA9IHNxcnQoc2lnbWEyX2UpKQ0KICANCiAgIyBGaW5kIGIxOg0KICBiMV9sb29wIDwtIHN1bSgoWCAtIG1lYW4oWCkpKihZX2xvb3AgLSBtZWFuKFlfbG9vcCkpKSAvIFNfWFgNCiAgDQogICMgRmluZGluZyBiMDoNCiAgYjBfbG9vcCA8LSBtZWFuKFlfbG9vcCkgLSBiMV9sb29wICogbWVhbihYKQ0KICANCiAgIyBDYWxjdWxhdGluZyB5X2hhdA0KICBZX2hhdF9sb29wIDwtIGIwX2xvb3AgKyBiMV9sb29wICogWA0KICANCiAgIyBSZXNpZHVhbHM6DQogIHJlc19sb29wIDwtIFlfbG9vcCAtIFlfaGF0X2xvb3ANCiAgDQogICMgU1NFOg0KICBTU0VfbG9vcCA8LSBzdW0ocmVzX2xvb3BeMikNCiAgDQogICMgTm93IHdlJ2xsIHNhdmUgdGhlIHJlc3VsdHMgaW4gdGhlIGZpcnN0IHJvdw0KICBzaW1fZGZbaSwgMjpuY29sKHNpbV9kZildIDwtIGMoYjBfbG9vcCwgYjFfbG9vcCwgU1NFX2xvb3ApDQp9DQoNCnRpYmJsZShzaW1fZGYpDQpgYGANCg0KIyMgRGlzdHJpYnV0aW9uIG9mICRiXzEkDQoNCkxldCdzIGNyZWF0ZSBhIGhpc3RvZ3JhbSBvZiBvdXIgc2ltdWxhdGVkIHNsb3BlcyBhbmQgc3VwZXJpbXBvc2UgYSBOb3JtYWwgY3VydmUgb24gaXQuDQoNClJlbWluZGVyOiBJZiB0aGUgdGhlb3J5IGlzIGNvcnJlY3QsIHRoZW46DQoNCiQkYl8xIFxzaW0gTlxsZWZ0KDQwLCBcZnJhY3s2NjB9ezMzMH1ccmlnaHQpJCQNCg0KYGBge3J9DQpnZ3Bsb3QoDQogIGRhdGEgPSBzaW1fZGYsDQogIG1hcHBpbmcgPSBhZXMoDQogICAgeCA9IGIxDQogICkNCikgKyANCiAgZ2VvbV9oaXN0b2dyYW0oDQogICAgIyBOZWVkIHRvIHN3YXAgZnJvbSBjb3VudCB0byBkZW5zaXR5IG9uIHRoZSB5LWF4aXMgdG8gcGxhY2UgYSBub3JtYWwgY3VydmUNCiAgICBtYXBwaW5nID0gYWVzKHkgPSBhZnRlcl9zdGF0KGRlbnNpdHkpKSwNCiAgICBiaW5zID0gNDAsDQogICAgZmlsbCA9ICdzdGVlbGJsdWUnLA0KICAgIGNvbG9yID0gJ3doaXRlJw0KICApICsgDQogICMgQWRkaW5nIHRoZSBOb3JtYWwgY3VydmU6DQogIHN0YXRfZnVuY3Rpb24oDQogICAgZnVuID0gZG5vcm0sDQogICAgYXJncyA9IGxpc3QobWVhbiA9IGJldGExLCBzZCA9IHNxcnQoc2lnbWEyX2UvU19YWCkpLA0KICAgIGNvbG9yID0gJ3JlZCcsDQogICAgbGluZXdpZHRoID0gMSwNCiAgICBhbHBoYSA9IDAuNQ0KICApICsgDQogIHRoZW1lX2J3KCkgICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICdIaXN0b2dyYW0gb2Ygc2ltdWxhdGVkIHNsb3BlcycNCiAgKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMoDQogICAgZXhwYW5kID0gYygwLCAwLCAwLjA1LCAwKQ0KICApDQogIA0KYGBgDQoNCkhpc3RvZ3JhbSBsb29rcyBwcmV0dHkgYWNjdXJhdGUhDQoNCldoYXQgYWJvdXQgdGhlIHRlc3Qgc3RhdGlzdGljcyBhbmQgcC12YWx1ZXM/IERvIHRoZXkgJ2xpbmUgdXAnIHdpdGggd2hhdCB0aGUgdGhlb3J5IHNheXM/DQoNCioqUmVzdWx0Kio6IElmICRIXzAkIGlzIHRydWUsIHRoZW4gdGhlIHAtdmFsdWUgaXMgYSByYW5kb20gdmFyaWFibGUgdGhhdCBmb2xsb3dzIGEgdW5pZm9ybSBkaXN0cmlidXRpb24gYmV0d2VlbiAwIGFuZCAxOg0KDQokJFx0ZXh0e3AtdmFsfSB8IEhfMCBcc2ltIFx0ZXh0e1VuaWZvcm19KDAsIDEpJCQNCg0KIyMjIFR5cGUgSSBlcnJvciByYXRlOiAkXHNpZ21hXjIkIGtub3duDQoNClNvIGlmIHdlIGNhbGN1bGF0ZSB0aGUgdGVzdCBzdGF0aXN0aWMgYW5kIHAtdmFsdWUgZm9yIGFsbCBvZiBvdXIgc2ltdWxhdGVkIHNhbXBsZXMsIHRoZW4gNSUgb2Ygb3VyIHNhbXBsZXMgc2hvdWxkIGdpdmUgdXMgYSBwLXZhbHVlIGJlbG93IDAuMDUgKG9yIHdoYXRldmVyIHNpZ25pZmljYW5jZSBsZXZlbCB5b3UgY2hvb3NlKQ0KDQokJFAoXHRleHR7cC12YWx9IDwgXGFscGhhKSA9IFxhbHBoYSQkDQoNCmBgYHtyIHBfdmFsX3NpbV9zaWdtYV9rbm93bn0NCiMgRmluZGluZyB0aGUgcC12YWx1ZXM6DQpzaW1fZGYgPC0gDQogIHNpbV9kZiB8Pg0KICBtdXRhdGUoDQogICAgIyBUZXN0IHN0YXRpc3RpYw0KICAgIHRlc3Rfc3RhdCA9IChiMSAtIGJldGExKSAvIHNxcnQoc2lnbWEyX2UvU19YWCksDQogICAgIyBOb3cgcC12YWx1ZSBmb3IgQmV0YTEgPVw9IDQwDQogICAgcF92YWxfc2lnbWEgPSAyKnBub3JtKGFicyh0ZXN0X3N0YXQpLCBsb3dlci50YWlsID0gRikNCiAgKQ0KDQpzaW1fZGYgfD4NCiAgZHBseXI6OnNlbGVjdChzYW1wbGUsIGIxLCB0ZXN0X3N0YXQsIHBfdmFsX3NpZ21hKSB8Pg0KICB0aWJibGUoKQ0KYGBgDQoNCkxldCdzIHBsb3QgdGhlbSBhbmQgY2FsY3VsYXRlIHRoZSBwcm9iYWJpbGl0eSBvZiByZWplY3RpbmcgYSB0cnVlICRIXzAkOg0KDQpgYGB7ciBwX3ZhbF9wbG90LCB3YXJuaW5nID0gRn0NCiMgQ2FsY3VsYXRpbmcgdGhlIHByb2JhYmlsaXR5IG9mIGEgdHlwZSAxIGVycm9yDQpwcm9iX3R5cGUxX2FscGhhXzUgPC0gbWVhbihzaW1fZGYkcF92YWxfc2lnbWEgPCAwLjA1KQ0KDQojIEhvdyB3aWRlIHRvIG1ha2UgZWFjaCBiaW4NCmJpbndpZHRoID0gMC4wMQ0KDQpnZ3Bsb3QoDQogIGRhdGEgPSBzaW1fZGYsDQogIG1hcHBpbmcgPSBhZXMoDQogICAgeCA9IHBfdmFsX3NpZ21hLA0KICAgIGZpbGwgPSBpZl9lbHNlKHBfdmFsX3NpZ21hIDwgMC4wNSwgJ3RvbWF0bycsICdzdGVlbGJsdWUnKQ0KICApDQopICsgDQogIGdlb21faGlzdG9ncmFtKA0KICAgICMgTmVlZCB0byBzd2FwIGZyb20gY291bnQgdG8gZGVuc2l0eSBvbiB0aGUgeS1heGlzIHRvIHBsYWNlIGEgbm9ybWFsIGN1cnZlDQogICAgbWFwcGluZyA9IGFlcyh5ID0gYWZ0ZXJfc3RhdChjb3VudCkgLyAobSAqIGJpbndpZHRoKSksDQogICAgYnJlYWtzID0gc2VxKDAsIDEsIGJpbndpZHRoKSwNCiAgICAjZmlsbCA9ICdzdGVlbGJsdWUnLA0KICAgIGNvbG9yID0gJ3doaXRlJw0KICApICsgDQogICMgQWRkaW5nIHRoZSBVbmlmb3JtICdjdXJ2ZSc6DQogIHN0YXRfZnVuY3Rpb24oDQogICAgZnVuID0gZHVuaWYsDQogICAgYXJncyA9IGxpc3QobWluID0gMCwgbWF4ID0gMSksDQogICAgY29sb3IgPSAncmVkJywNCiAgICBsaW5ld2lkdGggPSAxLA0KICAgIGFscGhhID0gMC41DQogICkgKyANCiAgIyBBZGRpbmcgdGhlIHByb2JhYmlsaXR5IG9mIFR5cGUgMQ0KICBsYWJzKA0KICAgIHRpdGxlID0gJ0Rpc3RyaWJ1dGlvbiBvZiBwLXZhbHVlcyB3aGVuIEhvIGlzIHRydWUgYW5kIHNpZ21hIGlzIGtub3duJywNCiAgICBzdWJ0aXRsZSA9IHBhc3RlKCdXaGVuIGFscGhhID0gNSUgdGhlIGNoYW5jZSBvZiBUeXBlIEkgZXJyb3IgaXMnLCANCiAgICAgICAgICAgICAgICAgICAgIHNjYWxlczo6cGVyY2VudChwcm9iX3R5cGUxX2FscGhhXzUpKSwNCiAgICBjYXB0aW9uID0gcGFzdGUoJ1Jlc3VsdHMgZnJvbSBhIHNpbXVsYXRpb24gb2YnLCBzY2FsZXM6OmNvbW1hKG0pLCANCiAgICAgICAgICAgICAgICAgICAgJ3NpbXVsYXRpb25zIGVhY2ggd2l0aCBhIHNhbXBsZSBzaXplIG9mJywgc2FtcGxlX3NpemUpLA0KICAgIHggPSAnUC12YWx1ZScsDQogICAgeSA9ICdEZW5zaXR5Jw0KICApICsNCiAgdGhlbWVfYncoKSAgKw0KICBzY2FsZV9maWxsX2lkZW50aXR5KCkgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKA0KICAgIGV4cGFuZCA9IGMoMCwgMCwgMC4wNSwgMCkNCiAgKQ0KYGBgDQoNClRoZSBkaXN0cmlidXRpb24gYWJvdmUgYXNzdW1lcyB3ZSBrbm93IHdoYXQgJFxzaWdtYV4yJCBpcywgd2hpY2ggd2UgdXN1YWxseSBkb24ndC4gV2hhdCBoYXBwZW5zIGlmIHdlIHN3YXAgJFxzaWdtYV4yJCB3aXRoIHRoZSBNU0U/DQoNCg0KDQoNCiMjIyBUeXBlIEkgZXJyb3IgcmF0ZTogJFxzaWdtYV4yJCAqKnVua25vd24qKg0KDQpJbnN0ZWFkIG9mIHVzaW5nIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgJGJfMSQsIHdlJ2xsIGhhdmUgdG8gdXNlIHRoZSBzdGFuZGFyZCBlcnJvcjoNCg0KJCRTRShiXzEpID0gXHNxcnR7XGZyYWN7TVNFfXtTX3tYWH19fSQkDQoNCldlIGNhbiBjYWxjdWxhdGUgdGhlICRNU0UkIGZvciBlYWNoIHNhbXBsZSBieToNCg0KJCRNU0UgPSBcZnJhY3tTU0V9e24tMn0kJA0KDQpTbyBsZXQncyBmaW5kIHRoZSBTdGFuZGFyZCBlcnJvciwgdGVzdCBzdGF0LCBhbmQgcC12YWx1ZSB1c2luZyB0aGUgJE1TRSQ6DQoNCiQkeiA9IFxmcmFje2JfMSAtIFxiZXRhX3sxLDB9fXtcc3FydHtcZnJhY3tNU0V9e1Nfe1hYfX19fSQkDQoNCmBgYHtyIHBfdmFsX3NpbV9NU0V9DQojIEZpbmRpbmcgdGhlIHAtdmFsdWVzOg0Kc2ltX2RmIDwtIA0KICBzaW1fZGYgfD4NCiAgbXV0YXRlKA0KICAgIE1TRSA9IFNTRSAvIChzYW1wbGVfc2l6ZSAtIDIpLA0KICAgICMgVGVzdCBzdGF0aXN0aWMgdXNpbmcgbXNlDQogICAgdGVzdF9zdGF0X21zZSA9IChiMSAtIGJldGExKSAvIHNxcnQoTVNFL1NfWFgpLA0KICAgICMgTm93IHAtdmFsdWUgZm9yIEJldGExID1cPSA0MA0KICAgIHBfdmFsX21zZSA9IDIqcG5vcm0oYWJzKHRlc3Rfc3RhdF9tc2UpLCBsb3dlci50YWlsID0gRikNCiAgKQ0KDQpzaW1fZGYgfD4NCiAgZHBseXI6OnNlbGVjdChzYW1wbGUsIGIxLCBNU0UsIHRlc3Rfc3RhdF9tc2UsIHBfdmFsX21zZSkgfD4NCiAgdGliYmxlKCkNCmBgYA0KDQojIyMjIFBsb3Qgb2YgdGhlIHRlc3Qgc3RhdGlzdGljIHVzaW5nIE1TRSBpbnN0ZWFkIG9mICRcc2lnbWFeMiQNCg0KVGhlIGhpc3RvZ3JhbSBvZiB0aGUgdGVzdCBzdGF0aXN0aWMgaXMgc2hvd24gYmVsb3cuIERvZXMgaXQgbG9vayBsaWtlIHRoZSBTdGFuZGFyZCBOb3JtYWwgZGlzdHJpYnV0aW9uIGZpdCB0aGUgaGlzdG9ncmFtPw0KDQpgYGB7ciB0ZXN0X3N0YXRfaGlzdF9tc2Vfbm9ybWFsfQ0KZ2dfdGVzdF9zdGF0X21zZV9oaXN0IDwtIA0KICBnZ3Bsb3QoDQogICAgIyBUaGVyZSBhcmUgYSBmZXcgZXh0cmVtZSB2YWx1ZXMsIHNvIHdlJ2xsIG9ubHkgbG9vayBhdCAtNSA8IHogPCA1DQogICAgZGF0YSA9IHNpbV9kZiB8Pg0KICAgICAgZmlsdGVyKGJldHdlZW4odGVzdF9zdGF0X21zZSwgLTUsIDUpKSwNCiAgICBtYXBwaW5nID0gYWVzKA0KICAgICAgeCA9IHRlc3Rfc3RhdF9tc2UNCiAgICApDQogICkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oDQogICAgIyBOZWVkIHRvIHN3YXAgZnJvbSBjb3VudCB0byBkZW5zaXR5IG9uIHRoZSB5LWF4aXMgdG8gcGxhY2UgYSBub3JtYWwgY3VydmUNCiAgICBtYXBwaW5nID0gYWVzKHkgPSBhZnRlcl9zdGF0KGRlbnNpdHkpKSwNCiAgICBiaW5zID0gNDAsDQogICAgZmlsbCA9ICdzdGVlbGJsdWUnLA0KICAgIGNvbG9yID0gJ3doaXRlJw0KICApICsgDQogIHRoZW1lX2J3KCkgICsNCiAgbGFicygNCiAgICB0aXRsZSA9IHBhc3RlKCdIaXN0b2dyYW0gb2YnLCBzY2FsZXM6OmNvbW1hKG0pLCAndGVzdCBzdGF0aXN0aWNzIGZvciB0aGUgc2xvcGUnKSwNCiAgICBzdWJ0aXRsZSA9ICdDYWxjdWxhdGVkIHVzaW5nIHRoZSBNU0UnLA0KICAgIHggPSAnVGVzdCBzdGF0aXN0aWNzJw0KICApICsgDQogIHNjYWxlX3lfY29udGludW91cygNCiAgICBleHBhbmQgPSBjKDAsIDAsIDAuMDUsIDApDQogICkgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKA0KICAgIGJyZWFrcyA9IC01OjUNCiAgKQ0KDQoNCmdnX3Rlc3Rfc3RhdF9tc2VfaGlzdCArIA0KICAjIEFkZGluZyB0aGUgTm9ybWFsIGN1cnZlOg0KICBzdGF0X2Z1bmN0aW9uKA0KICAgIGZ1biA9IGRub3JtLA0KICAgIGFyZ3MgPSBsaXN0KG1lYW4gPSAwLCBzZCA9IDEpLA0KICAgIGNvbG9yID0gJ3JlZCcsDQogICAgbGluZXdpZHRoID0gMSwNCiAgICBhbHBoYSA9IDAuNQ0KICApICsgDQogIGxhYnMoDQogICAgY2FwdGlvbiA9ICdTdGFuZGFyZCBOb3JtYWwgRGlzdHJpYnV0aW9uIE92ZXJsYXllZCBvbiB0aGUgSGlzdG9ncmFtJw0KICApDQpgYGANCg0KVGhlIE5vcm1hbCBjdXJ2ZSBsb29rcyBjbG9zZSwgYnV0IHRoZSBtaWRkbGUgYmFycyBhcmUgY29uc2lzdGVudGx5IGJlbG93IHRoZSBsaW5lIHdoaWxlIHRoZSB0YWlscyBhcmUgYWJvdmUgdGhlIGxpbmUuIFRoYXQgdHlwaWNhbGx5IGluZGljYXRlcyB0aGF0IGEgd2lkZXIgZGlzdHJpYnV0aW9uIGlzIG5lZWRlZCBpbnN0ZWFkIG9mIGEgTm9ybWFsIGRpc3RyaWJ1dGlvbi4NCg0KTGV0J3MgY2hlY2sgdGhlIHAtdmFsdWVzIGFzIHdlbGwhDQoNCiMjIyMgUGxvdCBvZiB0aGUgcC12YWx1ZXMgdXNpbmcgTVNFIGFuZCBOb3JtYWwgZGlzdHJpYnV0aW9uDQoNCkFnYWluLCBwbG90dGluZyB0aGVtIGFuZCBjYWxjdWxhdGUgdGhlIHByb2JhYmlsaXR5IG9mIHJlamVjdGluZyBhIHRydWUgJEhfMCQgYXNzdW1pbmcgdGhlIHRlc3Qgc3RhdGlzdGljIGZvbGxvd3MgYSBTdGFuZGFyZCBOb3JtYWw6DQoNCmBgYHtyIHBfdmFsX3Bsb3RfbXNlLCB3YXJuaW5nID0gRn0NCiMgQ2FsY3VsYXRpbmcgdGhlIHByb2JhYmlsaXR5IG9mIGEgdHlwZSAxIGVycm9yDQpwcm9iX3R5cGUxX2FscGhhXzVfbXNlIDwtIG1lYW4oc2ltX2RmJHBfdmFsX21zZSA8IDAuMDUpDQoNCiMgQ3JlYXRpbmcgdGhlIGdyYXBoDQpnZ3Bsb3QoDQogIGRhdGEgPSBzaW1fZGYsDQogIG1hcHBpbmcgPSBhZXMoDQogICAgeCA9IHBfdmFsX21zZSwNCiAgICBmaWxsID0gaWZfZWxzZShwX3ZhbF9tc2UgPCAwLjA1LCAndG9tYXRvJywgJ3N0ZWVsYmx1ZScpLA0KICAgIyBncm91cCA9IDENCiAgKQ0KKSArIA0KICBnZW9tX2hpc3RvZ3JhbSgNCiAgICAjIE5lZWQgdG8gc3dhcCBmcm9tIGNvdW50IHRvIGRlbnNpdHkgb24gdGhlIHktYXhpcyB0byBwbGFjZSBhIG5vcm1hbCBjdXJ2ZQ0KICAgIG1hcHBpbmcgPSBhZXMoeSA9IGFmdGVyX3N0YXQoY291bnQpIC8gKG0gKiBiaW53aWR0aCkpLA0KICAgIGJyZWFrcyA9IHNlcSgwLCAxLCBiaW53aWR0aCksDQogICAgY29sb3IgPSAnd2hpdGUnDQogICkgKyANCiAgIyBBZGRpbmcgdGhlIFVuaWZvcm0gJ2N1cnZlJzoNCiAgc3RhdF9mdW5jdGlvbigNCiAgICBmdW4gPSBkdW5pZiwNCiAgICBhcmdzID0gbGlzdChtaW4gPSAwLCBtYXggPSAxKSwNCiAgICBjb2xvciA9ICdyZWQnLA0KICAgIGxpbmV3aWR0aCA9IDEsDQogICAgYWxwaGEgPSAwLjUNCiAgKSArIA0KICAjIEFkZGluZyB0aGUgcHJvYmFiaWxpdHkgb2YgVHlwZSAxDQogIGxhYnMoDQogICAgdGl0bGUgPSAnRGlzdHJpYnV0aW9uIG9mIHAtdmFsdWVzIHdoZW4gSDAgaXMgdHJ1ZSwgdXNpbmcgYSBOb3JtYWwgd2l0aCBzaWdtYSB1bmtub3duJywNCiAgICBzdWJ0aXRsZSA9IHBhc3RlKCdhbHBoYSA9IDUlIGFuZCBUeXBlIEkgcHJvYmFiaWxpdHkgaXMnLCANCiAgICAgICAgICAgICAgICAgICAgIHNjYWxlczo6cGVyY2VudChwcm9iX3R5cGUxX2FscGhhXzVfbXNlKSksDQogICAgY2FwdGlvbiA9IHBhc3RlKCdSZXN1bHRzIGZyb20gYSBzaW11bGF0aW9uIG9mJywgc2NhbGVzOjpjb21tYShtKSwgDQogICAgICAgICAgICAgICAgICAgICdzaW11bGF0aW9ucyBlYWNoIHdpdGggYSBzYW1wbGUgc2l6ZSBvZicsIHNhbXBsZV9zaXplKSwNCiAgICB4ID0gJ1AtdmFsdWUnLA0KICAgIHkgPSAnRGVuc2l0eScNCiAgKSArDQogIHRoZW1lX2J3KCkgICsNCiAgc2NhbGVfZmlsbF9pZGVudGl0eSgpICsgDQogIHNjYWxlX3lfY29udGludW91cygNCiAgICBleHBhbmQgPSBjKDAsIDAsIDAuMDUsIDApDQogICkNCmBgYA0KDQpPdXIgY2hhbmNlIG9mIGEgVHlwZSBJIGVycm9yIGlzIGFsbW9zdCB0d2ljZSB0aGUgc2lnbmlmaWNhbmNlIGxldmVsISBXaHk/DQoNCiMjIyBQLXZhbHVlIHVzaW5nIGEgdC1kaXN0cmlidXRpb24NCg0KTGlrZSB3aGF0IHlvdSBkaWQgd2FhYWF5IGJhY2sgaW4geW91ciBpbnRyb2R1Y3Rvcnkgc3RhdHMgY2xhc3MsIHdoZW4geW91IHJlcGxhY2UgJFxzaWdtYV4yJCB3aXRoIGl0cyBzYW1wbGUgc3RhdGlzdGljIGVxdWl2YWxlbnQsIHRoZSB0ZXN0IHN0YXRpc3RpYyBpcyBubyBsb25nZXIgTm9ybWFsbHkgZGlzdHJpYnV0ZWQuDQoNCiFbXShodHRwczovL2VuY3J5cHRlZC10Ym4wLmdzdGF0aWMuY29tL2ltYWdlcz9xPXRibjpBTmQ5R2NRM2Y5SWc3QjVuZGRQdHVwYTlDc1hCbjVrV3dsWDh4U2lEMnBPaDRNTC1GQmlKTGJLUzhiYU1sam8mcz0xMCl7d2lkdGg9IjUwJSIgaGVpZ2h0PSI1MCUifQ0KDQpJbnN0ZWFkLCB0aGUgdGVzdCBzdGF0IHdpbGwgZm9sbG93IGEgXCpcKnQtZGlzdHJpYnV0aW9uXCogd2l0aCBkZWdyZWVzIG9mIGZyZWVkb20gZXF1YWwgdG8gdGhlIGRlbm9taW5hdG9yIG9mIHRoZSBmb3JtdWxhIHRvIGNhbGN1bGF0ZSAkXGhhdHtcc2lnbWF9XjIkOg0KDQokJFxoYXR7XHNpZ21hfV4yID0gXHNxcnR7XGZyYWN7U1NFfXtuIC0gMn19JCQNCg0KU28gaWYgd2UgdXNlICRNU0UkIGluIHBsYWNlIG9mICRcc2lnbWFeMiQsIHRoZW4gdGhlIHRlc3Qgc3RhdGlzdGljIHdpbGwgZm9sbG93IGEgdC1kaXN0cmlidXRpb24gd2l0aCAkbiAtIDIkIGRlZ3JlZXMgb2YgZnJlZWRvbSENCg0KIyMjIyBIaXN0b2dyYW0gb2YgdGhlIHRlc3Qgc3RhdGlzdGljIHdpdGggYSB0LWRpc3RyaWJ1dGlvbg0KDQpgYGB7ciB0ZXN0X3N0YXRfdF9kaXN0fQ0KZ2dfdGVzdF9zdGF0X21zZV9oaXN0ICsgDQogICMgQWRkaW5nIHRoZSBOb3JtYWwgY3VydmU6DQogIHN0YXRfZnVuY3Rpb24oDQogICAgZnVuID0gZHQsDQogICAgYXJncyA9IGxpc3QoZGYgPSBzYW1wbGVfc2l6ZSAtIDIpLA0KICAgIGNvbG9yID0gJ3JlZCcsDQogICAgbGluZXdpZHRoID0gMSwNCiAgICBhbHBoYSA9IDAuNQ0KICApICsgDQogIGxhYnMoDQogICAgY2FwdGlvbiA9IHBhc3RlKCd0LWRpc3RyaWJ1dGlvbiB3aXRoJywgc2FtcGxlX3NpemUgLSAyLCANCiAgICAgICAgICAgICAgICAgICAgJ2RmIG92ZXJsYXllZCBvbiB0aGUgaGlzdG9ncmFtJykNCiAgKQ0KYGBgDQoNClRoZSB0LWRpc3RyaWJ1dGlvbiBmaXRzIGdyZWF0IQ0KDQojIyMjIEhpc3RvZ3JhbSBvZiBwLXZhbHVlcyB1c2luZyBNU0UgYW5kIHQtZGlzdHJpYnV0aW9uDQoNClRoZSBjb2RlIGNodW5rIGJlbG93IHdpbGwgY2FsY3VsYXRlIHRoZSBwLXZhbHVlIHVzaW5nIHRoZSB0LWRpc3RyaWJ1dGlvbiB3aXRoIGRmID0gODoNCg0KYGBge3IgcF92YWxfc2ltX3RkaXN0fQ0KIyBGaW5kaW5nIHRoZSBwLXZhbHVlczoNCnNpbV9kZiA8LSANCiAgc2ltX2RmIHw+DQogIG11dGF0ZSgNCiAgICAjIE5vdyBwLXZhbHVlIGZvciBCZXRhMSA9XD0gNDAgdXNpbmcgYSB0LWRpc3RyaWJ1dGlvbg0KICAgIHRfcF92YWwgPSAyKnB0KGFicyh0ZXN0X3N0YXRfbXNlKSwgZGYgPSBzYW1wbGVfc2l6ZSAtIDIsIGxvd2VyLnRhaWwgPSBGKQ0KICApDQoNCnNpbV9kZiB8Pg0KICBkcGx5cjo6c2VsZWN0KHNhbXBsZSwgYjEsIE1TRSwgdGVzdF9zdGF0X21zZSwgdF9wX3ZhbCkgfD4NCiAgdGliYmxlKCkNCmBgYA0KDQpBZ2FpbiwgcGxvdHRpbmcgdGhlIHAtdmFsdWVzIGNhbGN1bGF0ZWQgYWJvdmUgYW5kIGNhbGN1bGF0ZSB0aGUgcHJvYmFiaWxpdHkgb2YgcmVqZWN0aW5nIGEgdHJ1ZSAkSF8wJCBhc3N1bWluZyB0aGUgdGVzdCBzdGF0aXN0aWMgZm9sbG93cyBhIHQtZGlzdHJpYnV0aW9uOg0KDQpgYGB7ciBwX3ZhbF9wbG90X3RkaXN0LCB3YXJuaW5nID0gRn0NCiMgQ2FsY3VsYXRpbmcgdGhlIHByb2JhYmlsaXR5IG9mIGEgdHlwZSAxIGVycm9yDQpwcm9iX3R5cGUxX2FscGhhXzVfdCA8LSBtZWFuKHNpbV9kZiR0X3BfdmFsIDwgMC4wNSkNCg0KIyBHcmFwaA0KZ2dwbG90KA0KICBkYXRhID0gc2ltX2RmLA0KICBtYXBwaW5nID0gYWVzKA0KICAgIHggPSB0X3BfdmFsLA0KICAgIGZpbGwgPSBpZl9lbHNlKHRfcF92YWwgPCAwLjA1LCAndG9tYXRvJywgJ3N0ZWVsYmx1ZScpLA0KICAgIyBncm91cCA9IDENCiAgKQ0KKSArIA0KICBnZW9tX2hpc3RvZ3JhbSgNCiAgICAjIE5lZWQgdG8gc3dhcCBmcm9tIGNvdW50IHRvIGRlbnNpdHkgb24gdGhlIHktYXhpcyB0byBwbGFjZSBhIG5vcm1hbCBjdXJ2ZQ0KICAgIG1hcHBpbmcgPSBhZXMoeSA9IGFmdGVyX3N0YXQoY291bnQpIC8gKG0gKiBiaW53aWR0aCkpLA0KICAgIGJyZWFrcyA9IHNlcSgwLCAxLCBiaW53aWR0aCksDQogICAgY29sb3IgPSAnd2hpdGUnDQogICkgKyANCiAgIyBBZGRpbmcgdGhlIFVuaWZvcm0gJ2N1cnZlJzoNCiAgc3RhdF9mdW5jdGlvbigNCiAgICBmdW4gPSBkdW5pZiwNCiAgICBhcmdzID0gbGlzdChtaW4gPSAwLCBtYXggPSAxKSwNCiAgICBjb2xvciA9ICdyZWQnLA0KICAgIGxpbmV3aWR0aCA9IDEsDQogICAgYWxwaGEgPSAwLjUNCiAgKSArIA0KICAjIEFkZGluZyB0aGUgcHJvYmFiaWxpdHkgb2YgVHlwZSAxDQogIGxhYnMoDQogICAgdGl0bGUgPSAnRGlzdHJpYnV0aW9uIG9mIHAtdmFsdWVzIHdoZW4gSG8gaXMgdHJ1ZSwgdXNpbmcgTVNFLCBhbmQgdC1kaXN0cmlidXRpb24nLA0KICAgIHN1YnRpdGxlID0gcGFzdGUoJ2FscGhhID0gNSUgYW5kIFR5cGUgSSBwcm9iYWJpbGl0eSBpcycsIA0KICAgICAgICAgICAgICAgICAgICAgc2NhbGVzOjpwZXJjZW50KHByb2JfdHlwZTFfYWxwaGFfNV90KSksDQogICAgY2FwdGlvbiA9IHBhc3RlKCdSZXN1bHRzIGZyb20gYSBzaW11bGF0aW9uIG9mJywgc2NhbGVzOjpjb21tYShtKSwgDQogICAgICAgICAgICAgICAgICAgICdzaW11bGF0aW9ucyBlYWNoIHdpdGggYSBzYW1wbGUgc2l6ZSBvZicsIHNhbXBsZV9zaXplKSwNCiAgICB4ID0gJ1AtdmFsdWUnLA0KICAgIHkgPSAnRGVuc2l0eScNCiAgKSArDQogIHRoZW1lX2J3KCkgICsNCiAgc2NhbGVfZmlsbF9pZGVudGl0eSgpICsgDQogIHNjYWxlX3lfY29udGludW91cygNCiAgICBleHBhbmQgPSBjKDAsIDAsIDAuMDUsIDApDQogICkNCmBgYA0KDQpOb3cgaXQgbWF0Y2hlcyENCg==