Libraries

# Data manipulation
library(plyr)
library(dplyr)
library(tidyr)
library(reshape2)
library(tibble)

# Pretty printing
library(scales)

# Fetching data
library(curl)

# Computing hashes, used for efficient model caching
library(hashr)

# Easier plotting
library(ggplot2); theme_set(theme_minimal())
library(likert)
library(bayesplot)
library(ggpubr)
library(HDInterval)
library(ggridges)

# Baysian modeling
library(brms)

Data fetching

We begin by importing the csv data from the data repository.

d.orig <- read.csv(curl("https://raw.githubusercontent.com/BrokenWindowsInvestigation/Data/master/data.csv"))
d.orig

Encoding the data with correct types

### Utility functions for encoding ###
encode.categorical <- function(column, categories) {
  factor(column, level = categories)
}

encode.bool <- function(column) {
  encode.categorical(column, c("true", "false"))
}
encode.logic <- function(column) {
  encode.categorical(column, c(TRUE, FALSE))
}

encode.orderedcategorical <- function(column, categories) {
  as.ordered(encode.categorical(column, categories))
}

encode.likert <- function(column) {
  encode.orderedcategorical(column, c(-3, -2, -1, 0, 1, 2, 3))
}

### Encode the original data ###
d <- data.frame(
  session = factor(d.orig$session), 
  
  time = d.orig$time,
  
  reused_logic_constructor = encode.bool(d.orig$reused_logic_constructor),
  reused_logic_validation  = encode.bool(d.orig$reused_logic_validation),
  
  equals.state   = encode.orderedcategorical(
    d.orig$equals_state, 
    c("Not implemented", "Duplicated", "Good")
  ),
  hashcode.state = encode.orderedcategorical(
    d.orig$hashcode_state, 
    c("Not implemented", "Duplicated", "Good")
  ),
  
  documentation = factor(d.orig$documentation),
  
  var_names_copied_all        = d.orig$var_names_copied_all,
  var_names_copied_good       = d.orig$var_names_copied_good,
  var_names_copied_good.ratio = d.orig$var_names_copied_good / d.orig$var_names_copied_all,
  var_names_new_all           = d.orig$var_names_new_all,
  var_names_new_good          = d.orig$var_names_new_good,
  var_names_new_good.ratio    = d.orig$var_names_new_good / d.orig$var_names_new_all,
  var_names_edited_all        = d.orig$var_names_edited_all,
  var_names_edited_good       = d.orig$var_names_edited_good,
  var_names_edited_good.ratio = d.orig$var_names_edited_good / d.orig$var_names_edited_all,
  
  sonarqube_issues          = 
    d.orig$sonarqube_issues_major + 
    d.orig$sonarqube_issues_minor + 
    d.orig$sonarqube_issues_info + 
    d.orig$sonarqube_issues_critical,
  sonarqube_issues.major    = d.orig$sonarqube_issues_major,
  sonarqube_issues.minor    = d.orig$sonarqube_issues_minor,
  sonarqube_issues.info     = d.orig$sonarqube_issues_info,
  sonarqube_issues.critical = d.orig$sonarqube_issues_critical,
  
  group = factor(d.orig$group),
  
  education_level = encode.orderedcategorical(d.orig$education_level, c(
    "None", 
    "Some bachelor studies", 
    "Bachelor degree", 
    "Some master studies", 
    "Master degree", 
    "Some Ph.D. studies", 
    "Ph. D."
  )),
  education_field = factor(d.orig$education_field),
  
  work_domain                 = factor(d.orig$work_domain),
  work_experience_programming = d.orig$work_experience_programming,
  work_experience_java        = d.orig$work_experience_java,
  
  workplace_pair_programming = encode.bool(d.orig$workplace_pair_programming),
  workplace_peer_review      = encode.bool(d.orig$workplace_peer_review),
  workplace_td_tracking      = encode.bool(d.orig$workplace_td_tracking),
  workplace_coding_standards = encode.bool(d.orig$workplace_coding_standards),
  
  task_completion = encode.orderedcategorical(d.orig$task_completion, c(
    "Not submitted", 
    "Does not compile", 
    "Invalid solution", 
    "Completed"
  )),
  
  quality_pre_task  = encode.likert(d.orig$quality_pre_task),
  quality_post_task = encode.likert(d.orig$quality_post_task),
  
  high_debt_version = encode.bool(d.orig$high_debt_version),
  scenario          = encode.categorical(d.orig$scenario, c("booking", "tickets")),
  order             = encode.orderedcategorical(d.orig$order, c(0, 1)),
  
  modified_lines         = d.orig$modified_lines,
  large_structure_change = encode.bool(d.orig$large_structure_change)
)

d$equals.exists <- encode.logic(d$equals.state != "Not implemented")
d$hashcode.exists <-encode.logic(d$hashcode.state != "Not implemented")

str(d)
## 'data.frame':    73 obs. of  41 variables:
##  $ session                    : Factor w/ 43 levels "6033c6fc5af2c702367b3a93",..: 1 1 2 2 3 3 4 4 5 6 ...
##  $ time                       : int  1055 2263 3249 12 1382 1197 3855 1984 0 5309 ...
##  $ reused_logic_constructor   : Factor w/ 2 levels "true","false": 1 2 2 2 1 1 1 1 2 2 ...
##  $ reused_logic_validation    : Factor w/ 2 levels "true","false": 1 2 2 2 1 1 1 1 2 1 ...
##  $ equals.state               : Ord.factor w/ 3 levels "Not implemented"<..: 2 2 1 1 1 3 1 3 1 3 ...
##  $ hashcode.state             : Ord.factor w/ 3 levels "Not implemented"<..: 2 2 1 1 1 3 1 3 1 3 ...
##  $ documentation              : Factor w/ 4 levels "Correct","Incorrect",..: 3 2 2 3 1 3 3 2 4 3 ...
##  $ var_names_copied_all       : int  6 12 15 0 8 7 5 7 0 9 ...
##  $ var_names_copied_good      : int  6 1 3 0 8 5 3 7 0 1 ...
##  $ var_names_copied_good.ratio: num  1 0.0833 0.2 NaN 1 ...
##  $ var_names_new_all          : int  4 3 4 0 4 2 4 2 0 22 ...
##  $ var_names_new_good         : int  4 1 0 0 4 2 4 2 0 11 ...
##  $ var_names_new_good.ratio   : num  1 0.333 0 NaN 1 ...
##  $ var_names_edited_all       : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ var_names_edited_good      : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ var_names_edited_good.ratio: num  NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ...
##  $ sonarqube_issues           : int  0 4 5 0 0 3 1 0 0 12 ...
##  $ sonarqube_issues.major     : int  0 2 5 0 0 3 1 0 0 8 ...
##  $ sonarqube_issues.minor     : int  0 2 0 0 0 0 0 0 0 4 ...
##  $ sonarqube_issues.info      : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ sonarqube_issues.critical  : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ group                      : Factor w/ 7 levels "code-interested",..: 7 7 7 7 7 7 7 7 7 7 ...
##  $ education_level            : Ord.factor w/ 6 levels "None"<"Some bachelor studies"<..: 3 3 4 4 4 4 2 2 2 4 ...
##  $ education_field            : Factor w/ 7 levels "Civil Engineering",..: 2 2 2 2 2 2 7 7 7 7 ...
##  $ work_domain                : Factor w/ 16 levels "Adtech","App",..: 12 12 3 3 7 7 3 3 12 16 ...
##  $ work_experience_programming: num  0 0 0.2 0.2 1 1 0 0 0 2 ...
##  $ work_experience_java       : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ workplace_pair_programming : Factor w/ 2 levels "true","false": 1 1 2 2 2 2 2 2 2 2 ...
##  $ workplace_peer_review      : Factor w/ 2 levels "true","false": 2 2 1 1 2 2 2 2 2 2 ...
##  $ workplace_td_tracking      : Factor w/ 2 levels "true","false": 2 2 2 2 2 2 2 2 2 2 ...
##  $ workplace_coding_standards : Factor w/ 2 levels "true","false": 2 2 1 1 2 2 1 1 2 2 ...
##  $ task_completion            : Ord.factor w/ 4 levels "Not submitted"<..: 2 4 3 1 4 4 4 4 1 4 ...
##  $ quality_pre_task           : Ord.factor w/ 7 levels "-3"<"-2"<"-1"<..: 4 3 6 6 4 2 2 6 4 3 ...
##  $ quality_post_task          : Ord.factor w/ 7 levels "-3"<"-2"<"-1"<..: 1 1 3 4 4 4 4 4 4 6 ...
##  $ high_debt_version          : Factor w/ 2 levels "true","false": 2 1 1 2 2 1 1 2 2 1 ...
##  $ scenario                   : Factor w/ 2 levels "booking","tickets": 1 2 1 2 1 2 1 2 1 1 ...
##  $ order                      : Ord.factor w/ 2 levels "0"<"1": 2 1 1 2 1 2 2 1 1 2 ...
##  $ modified_lines             : int  75 71 78 0 41 22 18 32 0 246 ...
##  $ large_structure_change     : Factor w/ 2 levels "true","false": 2 2 2 2 2 2 2 2 2 1 ...
##  $ equals.exists              : Factor w/ 2 levels "TRUE","FALSE": 1 1 2 2 2 1 2 1 2 1 ...
##  $ hashcode.exists            : Factor w/ 2 levels "TRUE","FALSE": 1 1 2 2 2 1 2 1 2 1 ...

Partial data sets and aggregates

For some models partial data sets and aggregates are needed.

Sessions as rows

d.sessions <- d %>% group_by(session) %>% dplyr::summarise(
  across(task_completion, min),
  across(c(
    education_level, 
    education_field, 
    work_domain, 
    group,
    work_experience_java, 
    work_experience_programming, 
    workplace_coding_standards, 
    workplace_pair_programming, 
    workplace_peer_review, 
    workplace_td_tracking
  ), first)
  ) 

d$work_experience_programming.s = scale(d$work_experience_programming)
d$work_experience_java.s = scale(d$work_experience_java)

d.sessions

Sessions as rows (only completed)

d.sessions.completed <- d.sessions %>% filter(task_completion == "Completed")

d.sessions.completed

Only completed

d.completed <- d %>% filter(task_completion == "Completed")

d.completed$work_experience_programming.s = scale(d.completed$work_experience_programming)
d.completed$work_experience_java.s = scale(d.completed$work_experience_java)
d.completed$time.s = scale(d.completed$time)
d.completed$sonarqube_issues.s = scale(d.completed$sonarqube_issues)

d.completed

Only both completed

d.both_completed <- d %>% semi_join(d.sessions.completed, by = "session")

d.both_completed$work_experience_programming.s = scale(d.both_completed$work_experience_programming)
d.both_completed$work_experience_java.s = scale(d.both_completed$work_experience_java)
d.both_completed$time.s = scale(d.both_completed$time)
d.both_completed$sonarqube_issues.s = scale(d.both_completed$sonarqube_issues)

d.both_completed

Model expansion utility function

The function extendable_model takes some basic arguments for creating brms models and returns a function that can be called with additioanl parameters to combine with those passed to extendable_model. The extendable_model takes the following arguments:

  • base_name is a name that is used to identify this extendable model while caching.
  • base_formula the formula that will be extended and passed to brms::brm, represented as string.
  • data the data frame to be passed to brms::brm.
  • base_priors (NULL) is a vector of priors to be passed to brms::brm.
  • base_control (NULL) is a vector of control options to be passed to brms::brm.

The returned function takes the following arguments:

  • additional_variables (NULL) a vector of aditional variables (predictors) to pass to pass to brms::brm in adition to base_formula.
  • additional_priors (NULL) a vector of additioanl priors to pass to brms::brm in adition to base_priors.
  • only_priors (FALSE) indicates if the model should be epty and not compiled, usefull to extract default priors of a model.
  • sample_prior ("no") is passed to the sample_prior of brms::brm.
  • control_override (NULL) takes a vector of control arguments for brms::brm that will override base_control.
extendable_model <- function(
  base_name, 
  base_formula, 
  family, 
  data, 
  base_priors = NULL, 
  base_control = NULL
) {
  function(
    additional_variables = NULL, 
    additional_priors = NULL, 
    only_priors = FALSE, 
    sample_prior = "no", 
    control_override = NULL
  ) {
    # Sort variable names for consistent caching and naming
    additional_variables.sorted <- sort(additional_variables)
    
    # Build priors
    priors <- base_priors
    if (!is.null(additional_priors)) {
      priors <- c(base_priors, additional_priors)
    }
    if (only_priors) {
      priors <- NULL
    }
    
    # Build formula
    additional_variables.formula <- paste(additional_variables.sorted, collapse = " + ")
    formula <- base_formula
    if (!is.null(additional_variables)) {
      formula <- paste(base_formula, additional_variables.formula, sep = " + ")
    }
    
    # Build cache file name
    additional_variables.name <- paste(additional_variables.sorted, collapse = ".")
    name <- base_name
    if (!is.null(additional_variables)) {
      name <- paste(base_name, hash(additional_variables.name), sep = ".")
    }
    name <- paste(name, paste("sample_priors-", sample_prior, sep = ""), sep = ".")
    name <- paste(name, paste("priors_hash-", hash(priors), sep = ""), sep = ".")
    name <- paste(name, paste("formula_hash-", hash(formula), sep = ""), sep = ".")
    
    # Get control options
    control <- base_control
    if (!is.null(control_override)) {
      control <- control_override
    }
    
    # Create and return the brms model
    brm(
      formula = as.formula(formula),
      family = family,
      data = as.data.frame(data),
      prior = priors,
      empty = only_priors,
      sample_prior = sample_prior,
      file = paste("fits", name, sep = "/"),
      file_refit = "on_change",
      seed = 20210421,
      control = control
    )
  }
}

Example usage:

## Not run
m.with <- extendable_model(
  base_name = "m", 
  base_formula = "time ~ 1",
  family = negbinomial(), 
  data = d.both_completed, 
  base_priors = c(
    prior(normal(0, 1), class = "Intercept")
  )
)

prior_summary(m.with(only_priors = TRUE))

pp_check(m.with(sample_prior = "only"), nsamples = 200)

summary(m.with())

pp_check(m.with(), nsamples = 200)

loo(
  m.with(),
  m.with("high_debt_version"),
  m.with(c("high_debt_version", "scenario"))
)
## End(Not run)
LS0tCnRpdGxlOiAiU2V0dXAgJiBEYXRhIFByZXBhcmF0aW9uIgphdXRob3I6IEhhbXB1cyBCcm9tYW4gJiBXaWxsaWFtIExldsOpbgpkYXRlOiAyMDIxLTA1Cm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDogCiAgICBwYW5kb2NfYXJnczogWyAiLW8iLCAiZG9jcy9zZXR1cC5odG1sIiBdCi0tLQojIyBMaWJyYXJpZXMKCmBgYHtyIGxvYWQtbGlicmFyaWVzLCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1zaG93JywgbWVzc2FnZT1GQUxTRX0KIyBEYXRhIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KHBseXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkodGliYmxlKQoKIyBQcmV0dHkgcHJpbnRpbmcKbGlicmFyeShzY2FsZXMpCgojIEZldGNoaW5nIGRhdGEKbGlicmFyeShjdXJsKQoKIyBDb21wdXRpbmcgaGFzaGVzLCB1c2VkIGZvciBlZmZpY2llbnQgbW9kZWwgY2FjaGluZwpsaWJyYXJ5KGhhc2hyKQoKIyBFYXNpZXIgcGxvdHRpbmcKbGlicmFyeShnZ3Bsb3QyKTsgdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKbGlicmFyeShsaWtlcnQpCmxpYnJhcnkoYmF5ZXNwbG90KQpsaWJyYXJ5KGdncHVicikKbGlicmFyeShIREludGVydmFsKQpsaWJyYXJ5KGdncmlkZ2VzKQoKIyBCYXlzaWFuIG1vZGVsaW5nCmxpYnJhcnkoYnJtcykKYGBgCgojIyBEYXRhIGZldGNoaW5nCgpXZSBiZWdpbiBieSBpbXBvcnRpbmcgdGhlIGNzdiBkYXRhIGZyb20gdGhlIFtkYXRhIHJlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9Ccm9rZW5XaW5kb3dzSW52ZXN0aWdhdGlvbi9EYXRhKS4KCmBgYHtyIGZldGNoLWRhdGF9CmQub3JpZyA8LSByZWFkLmNzdihjdXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQnJva2VuV2luZG93c0ludmVzdGlnYXRpb24vRGF0YS9tYXN0ZXIvZGF0YS5jc3YiKSkKZC5vcmlnCmBgYAoKIyMgRW5jb2RpbmcgdGhlIGRhdGEgd2l0aCBjb3JyZWN0IHR5cGVzCgpgYGB7ciBkYXRhLWVuY29kaW5nfQojIyMgVXRpbGl0eSBmdW5jdGlvbnMgZm9yIGVuY29kaW5nICMjIwplbmNvZGUuY2F0ZWdvcmljYWwgPC0gZnVuY3Rpb24oY29sdW1uLCBjYXRlZ29yaWVzKSB7CiAgZmFjdG9yKGNvbHVtbiwgbGV2ZWwgPSBjYXRlZ29yaWVzKQp9CgplbmNvZGUuYm9vbCA8LSBmdW5jdGlvbihjb2x1bW4pIHsKICBlbmNvZGUuY2F0ZWdvcmljYWwoY29sdW1uLCBjKCJ0cnVlIiwgImZhbHNlIikpCn0KZW5jb2RlLmxvZ2ljIDwtIGZ1bmN0aW9uKGNvbHVtbikgewogIGVuY29kZS5jYXRlZ29yaWNhbChjb2x1bW4sIGMoVFJVRSwgRkFMU0UpKQp9CgplbmNvZGUub3JkZXJlZGNhdGVnb3JpY2FsIDwtIGZ1bmN0aW9uKGNvbHVtbiwgY2F0ZWdvcmllcykgewogIGFzLm9yZGVyZWQoZW5jb2RlLmNhdGVnb3JpY2FsKGNvbHVtbiwgY2F0ZWdvcmllcykpCn0KCmVuY29kZS5saWtlcnQgPC0gZnVuY3Rpb24oY29sdW1uKSB7CiAgZW5jb2RlLm9yZGVyZWRjYXRlZ29yaWNhbChjb2x1bW4sIGMoLTMsIC0yLCAtMSwgMCwgMSwgMiwgMykpCn0KCiMjIyBFbmNvZGUgdGhlIG9yaWdpbmFsIGRhdGEgIyMjCmQgPC0gZGF0YS5mcmFtZSgKICBzZXNzaW9uID0gZmFjdG9yKGQub3JpZyRzZXNzaW9uKSwgCiAgCiAgdGltZSA9IGQub3JpZyR0aW1lLAogIAogIHJldXNlZF9sb2dpY19jb25zdHJ1Y3RvciA9IGVuY29kZS5ib29sKGQub3JpZyRyZXVzZWRfbG9naWNfY29uc3RydWN0b3IpLAogIHJldXNlZF9sb2dpY192YWxpZGF0aW9uICA9IGVuY29kZS5ib29sKGQub3JpZyRyZXVzZWRfbG9naWNfdmFsaWRhdGlvbiksCiAgCiAgZXF1YWxzLnN0YXRlICAgPSBlbmNvZGUub3JkZXJlZGNhdGVnb3JpY2FsKAogICAgZC5vcmlnJGVxdWFsc19zdGF0ZSwgCiAgICBjKCJOb3QgaW1wbGVtZW50ZWQiLCAiRHVwbGljYXRlZCIsICJHb29kIikKICApLAogIGhhc2hjb2RlLnN0YXRlID0gZW5jb2RlLm9yZGVyZWRjYXRlZ29yaWNhbCgKICAgIGQub3JpZyRoYXNoY29kZV9zdGF0ZSwgCiAgICBjKCJOb3QgaW1wbGVtZW50ZWQiLCAiRHVwbGljYXRlZCIsICJHb29kIikKICApLAogIAogIGRvY3VtZW50YXRpb24gPSBmYWN0b3IoZC5vcmlnJGRvY3VtZW50YXRpb24pLAogIAogIHZhcl9uYW1lc19jb3BpZWRfYWxsICAgICAgICA9IGQub3JpZyR2YXJfbmFtZXNfY29waWVkX2FsbCwKICB2YXJfbmFtZXNfY29waWVkX2dvb2QgICAgICAgPSBkLm9yaWckdmFyX25hbWVzX2NvcGllZF9nb29kLAogIHZhcl9uYW1lc19jb3BpZWRfZ29vZC5yYXRpbyA9IGQub3JpZyR2YXJfbmFtZXNfY29waWVkX2dvb2QgLyBkLm9yaWckdmFyX25hbWVzX2NvcGllZF9hbGwsCiAgdmFyX25hbWVzX25ld19hbGwgICAgICAgICAgID0gZC5vcmlnJHZhcl9uYW1lc19uZXdfYWxsLAogIHZhcl9uYW1lc19uZXdfZ29vZCAgICAgICAgICA9IGQub3JpZyR2YXJfbmFtZXNfbmV3X2dvb2QsCiAgdmFyX25hbWVzX25ld19nb29kLnJhdGlvICAgID0gZC5vcmlnJHZhcl9uYW1lc19uZXdfZ29vZCAvIGQub3JpZyR2YXJfbmFtZXNfbmV3X2FsbCwKICB2YXJfbmFtZXNfZWRpdGVkX2FsbCAgICAgICAgPSBkLm9yaWckdmFyX25hbWVzX2VkaXRlZF9hbGwsCiAgdmFyX25hbWVzX2VkaXRlZF9nb29kICAgICAgID0gZC5vcmlnJHZhcl9uYW1lc19lZGl0ZWRfZ29vZCwKICB2YXJfbmFtZXNfZWRpdGVkX2dvb2QucmF0aW8gPSBkLm9yaWckdmFyX25hbWVzX2VkaXRlZF9nb29kIC8gZC5vcmlnJHZhcl9uYW1lc19lZGl0ZWRfYWxsLAogIAogIHNvbmFycXViZV9pc3N1ZXMgICAgICAgICAgPSAKICAgIGQub3JpZyRzb25hcnF1YmVfaXNzdWVzX21ham9yICsgCiAgICBkLm9yaWckc29uYXJxdWJlX2lzc3Vlc19taW5vciArIAogICAgZC5vcmlnJHNvbmFycXViZV9pc3N1ZXNfaW5mbyArIAogICAgZC5vcmlnJHNvbmFycXViZV9pc3N1ZXNfY3JpdGljYWwsCiAgc29uYXJxdWJlX2lzc3Vlcy5tYWpvciAgICA9IGQub3JpZyRzb25hcnF1YmVfaXNzdWVzX21ham9yLAogIHNvbmFycXViZV9pc3N1ZXMubWlub3IgICAgPSBkLm9yaWckc29uYXJxdWJlX2lzc3Vlc19taW5vciwKICBzb25hcnF1YmVfaXNzdWVzLmluZm8gICAgID0gZC5vcmlnJHNvbmFycXViZV9pc3N1ZXNfaW5mbywKICBzb25hcnF1YmVfaXNzdWVzLmNyaXRpY2FsID0gZC5vcmlnJHNvbmFycXViZV9pc3N1ZXNfY3JpdGljYWwsCiAgCiAgZ3JvdXAgPSBmYWN0b3IoZC5vcmlnJGdyb3VwKSwKICAKICBlZHVjYXRpb25fbGV2ZWwgPSBlbmNvZGUub3JkZXJlZGNhdGVnb3JpY2FsKGQub3JpZyRlZHVjYXRpb25fbGV2ZWwsIGMoCiAgICAiTm9uZSIsIAogICAgIlNvbWUgYmFjaGVsb3Igc3R1ZGllcyIsIAogICAgIkJhY2hlbG9yIGRlZ3JlZSIsIAogICAgIlNvbWUgbWFzdGVyIHN0dWRpZXMiLCAKICAgICJNYXN0ZXIgZGVncmVlIiwgCiAgICAiU29tZSBQaC5ELiBzdHVkaWVzIiwgCiAgICAiUGguIEQuIgogICkpLAogIGVkdWNhdGlvbl9maWVsZCA9IGZhY3RvcihkLm9yaWckZWR1Y2F0aW9uX2ZpZWxkKSwKICAKICB3b3JrX2RvbWFpbiAgICAgICAgICAgICAgICAgPSBmYWN0b3IoZC5vcmlnJHdvcmtfZG9tYWluKSwKICB3b3JrX2V4cGVyaWVuY2VfcHJvZ3JhbW1pbmcgPSBkLm9yaWckd29ya19leHBlcmllbmNlX3Byb2dyYW1taW5nLAogIHdvcmtfZXhwZXJpZW5jZV9qYXZhICAgICAgICA9IGQub3JpZyR3b3JrX2V4cGVyaWVuY2VfamF2YSwKICAKICB3b3JrcGxhY2VfcGFpcl9wcm9ncmFtbWluZyA9IGVuY29kZS5ib29sKGQub3JpZyR3b3JrcGxhY2VfcGFpcl9wcm9ncmFtbWluZyksCiAgd29ya3BsYWNlX3BlZXJfcmV2aWV3ICAgICAgPSBlbmNvZGUuYm9vbChkLm9yaWckd29ya3BsYWNlX3BlZXJfcmV2aWV3KSwKICB3b3JrcGxhY2VfdGRfdHJhY2tpbmcgICAgICA9IGVuY29kZS5ib29sKGQub3JpZyR3b3JrcGxhY2VfdGRfdHJhY2tpbmcpLAogIHdvcmtwbGFjZV9jb2Rpbmdfc3RhbmRhcmRzID0gZW5jb2RlLmJvb2woZC5vcmlnJHdvcmtwbGFjZV9jb2Rpbmdfc3RhbmRhcmRzKSwKICAKICB0YXNrX2NvbXBsZXRpb24gPSBlbmNvZGUub3JkZXJlZGNhdGVnb3JpY2FsKGQub3JpZyR0YXNrX2NvbXBsZXRpb24sIGMoCiAgICAiTm90IHN1Ym1pdHRlZCIsIAogICAgIkRvZXMgbm90IGNvbXBpbGUiLCAKICAgICJJbnZhbGlkIHNvbHV0aW9uIiwgCiAgICAiQ29tcGxldGVkIgogICkpLAogIAogIHF1YWxpdHlfcHJlX3Rhc2sgID0gZW5jb2RlLmxpa2VydChkLm9yaWckcXVhbGl0eV9wcmVfdGFzayksCiAgcXVhbGl0eV9wb3N0X3Rhc2sgPSBlbmNvZGUubGlrZXJ0KGQub3JpZyRxdWFsaXR5X3Bvc3RfdGFzayksCiAgCiAgaGlnaF9kZWJ0X3ZlcnNpb24gPSBlbmNvZGUuYm9vbChkLm9yaWckaGlnaF9kZWJ0X3ZlcnNpb24pLAogIHNjZW5hcmlvICAgICAgICAgID0gZW5jb2RlLmNhdGVnb3JpY2FsKGQub3JpZyRzY2VuYXJpbywgYygiYm9va2luZyIsICJ0aWNrZXRzIikpLAogIG9yZGVyICAgICAgICAgICAgID0gZW5jb2RlLm9yZGVyZWRjYXRlZ29yaWNhbChkLm9yaWckb3JkZXIsIGMoMCwgMSkpLAogIAogIG1vZGlmaWVkX2xpbmVzICAgICAgICAgPSBkLm9yaWckbW9kaWZpZWRfbGluZXMsCiAgbGFyZ2Vfc3RydWN0dXJlX2NoYW5nZSA9IGVuY29kZS5ib29sKGQub3JpZyRsYXJnZV9zdHJ1Y3R1cmVfY2hhbmdlKQopCgpkJGVxdWFscy5leGlzdHMgPC0gZW5jb2RlLmxvZ2ljKGQkZXF1YWxzLnN0YXRlICE9ICJOb3QgaW1wbGVtZW50ZWQiKQpkJGhhc2hjb2RlLmV4aXN0cyA8LWVuY29kZS5sb2dpYyhkJGhhc2hjb2RlLnN0YXRlICE9ICJOb3QgaW1wbGVtZW50ZWQiKQoKc3RyKGQpCmBgYAoKIyMgUGFydGlhbCBkYXRhIHNldHMgYW5kIGFnZ3JlZ2F0ZXMgey50YWJzZXR9CgpGb3Igc29tZSBtb2RlbHMgcGFydGlhbCBkYXRhIHNldHMgYW5kIGFnZ3JlZ2F0ZXMgYXJlIG5lZWRlZC4KCiMjIyBTZXNzaW9ucyBhcyByb3dzCgpgYGB7ciBwYXJ0aWFsLXNlc3Npb25zfQpkLnNlc3Npb25zIDwtIGQgJT4lIGdyb3VwX2J5KHNlc3Npb24pICU+JSBkcGx5cjo6c3VtbWFyaXNlKAogIGFjcm9zcyh0YXNrX2NvbXBsZXRpb24sIG1pbiksCiAgYWNyb3NzKGMoCiAgICBlZHVjYXRpb25fbGV2ZWwsIAogICAgZWR1Y2F0aW9uX2ZpZWxkLCAKICAgIHdvcmtfZG9tYWluLCAKICAgIGdyb3VwLAogICAgd29ya19leHBlcmllbmNlX2phdmEsIAogICAgd29ya19leHBlcmllbmNlX3Byb2dyYW1taW5nLCAKICAgIHdvcmtwbGFjZV9jb2Rpbmdfc3RhbmRhcmRzLCAKICAgIHdvcmtwbGFjZV9wYWlyX3Byb2dyYW1taW5nLCAKICAgIHdvcmtwbGFjZV9wZWVyX3JldmlldywgCiAgICB3b3JrcGxhY2VfdGRfdHJhY2tpbmcKICApLCBmaXJzdCkKICApIAoKZCR3b3JrX2V4cGVyaWVuY2VfcHJvZ3JhbW1pbmcucyA9IHNjYWxlKGQkd29ya19leHBlcmllbmNlX3Byb2dyYW1taW5nKQpkJHdvcmtfZXhwZXJpZW5jZV9qYXZhLnMgPSBzY2FsZShkJHdvcmtfZXhwZXJpZW5jZV9qYXZhKQoKZC5zZXNzaW9ucwpgYGAKCiMjIyBTZXNzaW9ucyBhcyByb3dzIChvbmx5IGNvbXBsZXRlZCkKCmBgYHtyIHBhcnRpYWwtc2Vzc2lvbnMtY29tcGxldGVkfQpkLnNlc3Npb25zLmNvbXBsZXRlZCA8LSBkLnNlc3Npb25zICU+JSBmaWx0ZXIodGFza19jb21wbGV0aW9uID09ICJDb21wbGV0ZWQiKQoKZC5zZXNzaW9ucy5jb21wbGV0ZWQKYGBgCgojIyMgT25seSBjb21wbGV0ZWQKCmBgYHtyIHBhcnRpYWwtc3VibWl0dGVkfQpkLmNvbXBsZXRlZCA8LSBkICU+JSBmaWx0ZXIodGFza19jb21wbGV0aW9uID09ICJDb21wbGV0ZWQiKQoKZC5jb21wbGV0ZWQkd29ya19leHBlcmllbmNlX3Byb2dyYW1taW5nLnMgPSBzY2FsZShkLmNvbXBsZXRlZCR3b3JrX2V4cGVyaWVuY2VfcHJvZ3JhbW1pbmcpCmQuY29tcGxldGVkJHdvcmtfZXhwZXJpZW5jZV9qYXZhLnMgPSBzY2FsZShkLmNvbXBsZXRlZCR3b3JrX2V4cGVyaWVuY2VfamF2YSkKZC5jb21wbGV0ZWQkdGltZS5zID0gc2NhbGUoZC5jb21wbGV0ZWQkdGltZSkKZC5jb21wbGV0ZWQkc29uYXJxdWJlX2lzc3Vlcy5zID0gc2NhbGUoZC5jb21wbGV0ZWQkc29uYXJxdWJlX2lzc3VlcykKCmQuY29tcGxldGVkCmBgYAoKIyMjIE9ubHkgYm90aCBjb21wbGV0ZWQKYGBge3IgcGFydGlhbC1zdWJtaXR0ZWQtYm90aH0KZC5ib3RoX2NvbXBsZXRlZCA8LSBkICU+JSBzZW1pX2pvaW4oZC5zZXNzaW9ucy5jb21wbGV0ZWQsIGJ5ID0gInNlc3Npb24iKQoKZC5ib3RoX2NvbXBsZXRlZCR3b3JrX2V4cGVyaWVuY2VfcHJvZ3JhbW1pbmcucyA9IHNjYWxlKGQuYm90aF9jb21wbGV0ZWQkd29ya19leHBlcmllbmNlX3Byb2dyYW1taW5nKQpkLmJvdGhfY29tcGxldGVkJHdvcmtfZXhwZXJpZW5jZV9qYXZhLnMgPSBzY2FsZShkLmJvdGhfY29tcGxldGVkJHdvcmtfZXhwZXJpZW5jZV9qYXZhKQpkLmJvdGhfY29tcGxldGVkJHRpbWUucyA9IHNjYWxlKGQuYm90aF9jb21wbGV0ZWQkdGltZSkKZC5ib3RoX2NvbXBsZXRlZCRzb25hcnF1YmVfaXNzdWVzLnMgPSBzY2FsZShkLmJvdGhfY29tcGxldGVkJHNvbmFycXViZV9pc3N1ZXMpCgpkLmJvdGhfY29tcGxldGVkCmBgYAoKIyMgTW9kZWwgZXhwYW5zaW9uIHV0aWxpdHkgZnVuY3Rpb24KClRoZSBmdW5jdGlvbiBgZXh0ZW5kYWJsZV9tb2RlbGAgdGFrZXMgc29tZSBiYXNpYyBhcmd1bWVudHMgZm9yIGNyZWF0aW5nIGJybXMgbW9kZWxzIGFuZCByZXR1cm5zIGEgZnVuY3Rpb24gdGhhdCBjYW4gYmUgY2FsbGVkIHdpdGggYWRkaXRpb2FubCBwYXJhbWV0ZXJzIHRvIGNvbWJpbmUgd2l0aCB0aG9zZSBwYXNzZWQgdG8gYGV4dGVuZGFibGVfbW9kZWxgLiBUaGUgYGV4dGVuZGFibGVfbW9kZWxgIHRha2VzIHRoZSBmb2xsb3dpbmcgYXJndW1lbnRzOgoKKiBgYmFzZV9uYW1lYCBpcyBhIG5hbWUgdGhhdCBpcyB1c2VkIHRvIGlkZW50aWZ5IHRoaXMgZXh0ZW5kYWJsZSBtb2RlbCB3aGlsZSBjYWNoaW5nLgoqIGBiYXNlX2Zvcm11bGFgIHRoZSBmb3JtdWxhIHRoYXQgd2lsbCBiZSBleHRlbmRlZCBhbmQgcGFzc2VkIHRvIGBicm1zOjpicm1gLCByZXByZXNlbnRlZCBhcyBzdHJpbmcuCiogYGRhdGFgIHRoZSBkYXRhIGZyYW1lIHRvIGJlIHBhc3NlZCB0byBgYnJtczo6YnJtYC4KKiBgYmFzZV9wcmlvcnNgIChgTlVMTGApIGlzIGEgdmVjdG9yIG9mIHByaW9ycyB0byBiZSBwYXNzZWQgdG8gYGJybXM6OmJybWAuCiogYGJhc2VfY29udHJvbGAgKGBOVUxMYCkgaXMgYSB2ZWN0b3Igb2YgY29udHJvbCBvcHRpb25zIHRvIGJlIHBhc3NlZCB0byBgYnJtczo6YnJtYC4KClRoZSByZXR1cm5lZCBmdW5jdGlvbiB0YWtlcyB0aGUgZm9sbG93aW5nIGFyZ3VtZW50czoKCiogYGFkZGl0aW9uYWxfdmFyaWFibGVzYCAoYE5VTExgKSBhIHZlY3RvciBvZiBhZGl0aW9uYWwgdmFyaWFibGVzIChwcmVkaWN0b3JzKSB0byBwYXNzIHRvIHBhc3MgdG8gYGJybXM6OmJybWAgaW4gYWRpdGlvbiB0byBgYmFzZV9mb3JtdWxhYC4KKiBgYWRkaXRpb25hbF9wcmlvcnNgIChgTlVMTGApIGEgdmVjdG9yIG9mIGFkZGl0aW9hbmwgcHJpb3JzIHRvIHBhc3MgdG8gYGJybXM6OmJybWAgaW4gYWRpdGlvbiB0byBgYmFzZV9wcmlvcnNgLgoqIGBvbmx5X3ByaW9yc2AgKGBGQUxTRWApIGluZGljYXRlcyBpZiB0aGUgbW9kZWwgc2hvdWxkIGJlIGVwdHkgYW5kIG5vdCBjb21waWxlZCwgdXNlZnVsbCB0byBleHRyYWN0IGRlZmF1bHQgcHJpb3JzIG9mIGEgbW9kZWwuCiogYHNhbXBsZV9wcmlvcmAgKGAibm8iYCkgaXMgcGFzc2VkIHRvIHRoZSBgc2FtcGxlX3ByaW9yYCBvZiBgYnJtczo6YnJtYC4KKiBgY29udHJvbF9vdmVycmlkZWAgKGBOVUxMYCkgdGFrZXMgYSB2ZWN0b3Igb2YgYGNvbnRyb2xgIGFyZ3VtZW50cyBmb3IgYGJybXM6OmJybWAgdGhhdCB3aWxsIG92ZXJyaWRlIGBiYXNlX2NvbnRyb2xgLgoKYGBge3IgZXh0ZW5kYWJsZS1tb2RlbH0KZXh0ZW5kYWJsZV9tb2RlbCA8LSBmdW5jdGlvbigKICBiYXNlX25hbWUsIAogIGJhc2VfZm9ybXVsYSwgCiAgZmFtaWx5LCAKICBkYXRhLCAKICBiYXNlX3ByaW9ycyA9IE5VTEwsIAogIGJhc2VfY29udHJvbCA9IE5VTEwKKSB7CiAgZnVuY3Rpb24oCiAgICBhZGRpdGlvbmFsX3ZhcmlhYmxlcyA9IE5VTEwsIAogICAgYWRkaXRpb25hbF9wcmlvcnMgPSBOVUxMLCAKICAgIG9ubHlfcHJpb3JzID0gRkFMU0UsIAogICAgc2FtcGxlX3ByaW9yID0gIm5vIiwgCiAgICBjb250cm9sX292ZXJyaWRlID0gTlVMTAogICkgewogICAgIyBTb3J0IHZhcmlhYmxlIG5hbWVzIGZvciBjb25zaXN0ZW50IGNhY2hpbmcgYW5kIG5hbWluZwogICAgYWRkaXRpb25hbF92YXJpYWJsZXMuc29ydGVkIDwtIHNvcnQoYWRkaXRpb25hbF92YXJpYWJsZXMpCiAgICAKICAgICMgQnVpbGQgcHJpb3JzCiAgICBwcmlvcnMgPC0gYmFzZV9wcmlvcnMKICAgIGlmICghaXMubnVsbChhZGRpdGlvbmFsX3ByaW9ycykpIHsKICAgICAgcHJpb3JzIDwtIGMoYmFzZV9wcmlvcnMsIGFkZGl0aW9uYWxfcHJpb3JzKQogICAgfQogICAgaWYgKG9ubHlfcHJpb3JzKSB7CiAgICAgIHByaW9ycyA8LSBOVUxMCiAgICB9CiAgICAKICAgICMgQnVpbGQgZm9ybXVsYQogICAgYWRkaXRpb25hbF92YXJpYWJsZXMuZm9ybXVsYSA8LSBwYXN0ZShhZGRpdGlvbmFsX3ZhcmlhYmxlcy5zb3J0ZWQsIGNvbGxhcHNlID0gIiArICIpCiAgICBmb3JtdWxhIDwtIGJhc2VfZm9ybXVsYQogICAgaWYgKCFpcy5udWxsKGFkZGl0aW9uYWxfdmFyaWFibGVzKSkgewogICAgICBmb3JtdWxhIDwtIHBhc3RlKGJhc2VfZm9ybXVsYSwgYWRkaXRpb25hbF92YXJpYWJsZXMuZm9ybXVsYSwgc2VwID0gIiArICIpCiAgICB9CiAgICAKICAgICMgQnVpbGQgY2FjaGUgZmlsZSBuYW1lCiAgICBhZGRpdGlvbmFsX3ZhcmlhYmxlcy5uYW1lIDwtIHBhc3RlKGFkZGl0aW9uYWxfdmFyaWFibGVzLnNvcnRlZCwgY29sbGFwc2UgPSAiLiIpCiAgICBuYW1lIDwtIGJhc2VfbmFtZQogICAgaWYgKCFpcy5udWxsKGFkZGl0aW9uYWxfdmFyaWFibGVzKSkgewogICAgICBuYW1lIDwtIHBhc3RlKGJhc2VfbmFtZSwgaGFzaChhZGRpdGlvbmFsX3ZhcmlhYmxlcy5uYW1lKSwgc2VwID0gIi4iKQogICAgfQogICAgbmFtZSA8LSBwYXN0ZShuYW1lLCBwYXN0ZSgic2FtcGxlX3ByaW9ycy0iLCBzYW1wbGVfcHJpb3IsIHNlcCA9ICIiKSwgc2VwID0gIi4iKQogICAgbmFtZSA8LSBwYXN0ZShuYW1lLCBwYXN0ZSgicHJpb3JzX2hhc2gtIiwgaGFzaChwcmlvcnMpLCBzZXAgPSAiIiksIHNlcCA9ICIuIikKICAgIG5hbWUgPC0gcGFzdGUobmFtZSwgcGFzdGUoImZvcm11bGFfaGFzaC0iLCBoYXNoKGZvcm11bGEpLCBzZXAgPSAiIiksIHNlcCA9ICIuIikKICAgIAogICAgIyBHZXQgY29udHJvbCBvcHRpb25zCiAgICBjb250cm9sIDwtIGJhc2VfY29udHJvbAogICAgaWYgKCFpcy5udWxsKGNvbnRyb2xfb3ZlcnJpZGUpKSB7CiAgICAgIGNvbnRyb2wgPC0gY29udHJvbF9vdmVycmlkZQogICAgfQogICAgCiAgICAjIENyZWF0ZSBhbmQgcmV0dXJuIHRoZSBicm1zIG1vZGVsCiAgICBicm0oCiAgICAgIGZvcm11bGEgPSBhcy5mb3JtdWxhKGZvcm11bGEpLAogICAgICBmYW1pbHkgPSBmYW1pbHksCiAgICAgIGRhdGEgPSBhcy5kYXRhLmZyYW1lKGRhdGEpLAogICAgICBwcmlvciA9IHByaW9ycywKICAgICAgZW1wdHkgPSBvbmx5X3ByaW9ycywKICAgICAgc2FtcGxlX3ByaW9yID0gc2FtcGxlX3ByaW9yLAogICAgICBmaWxlID0gcGFzdGUoImZpdHMiLCBuYW1lLCBzZXAgPSAiLyIpLAogICAgICBmaWxlX3JlZml0ID0gIm9uX2NoYW5nZSIsCiAgICAgIHNlZWQgPSAyMDIxMDQyMSwKICAgICAgY29udHJvbCA9IGNvbnRyb2wKICAgICkKICB9Cn0KYGBgCgpFeGFtcGxlIHVzYWdlOgoKYGBge3IsIGV2YWw9RkFMU0UsIGNsYXNzLnNvdXJjZSA9ICdmb2xkLXNob3cnfQojIyBOb3QgcnVuCm0ud2l0aCA8LSBleHRlbmRhYmxlX21vZGVsKAogIGJhc2VfbmFtZSA9ICJtIiwgCiAgYmFzZV9mb3JtdWxhID0gInRpbWUgfiAxIiwKICBmYW1pbHkgPSBuZWdiaW5vbWlhbCgpLCAKICBkYXRhID0gZC5ib3RoX2NvbXBsZXRlZCwgCiAgYmFzZV9wcmlvcnMgPSBjKAogICAgcHJpb3Iobm9ybWFsKDAsIDEpLCBjbGFzcyA9ICJJbnRlcmNlcHQiKQogICkKKQoKcHJpb3Jfc3VtbWFyeShtLndpdGgob25seV9wcmlvcnMgPSBUUlVFKSkKCnBwX2NoZWNrKG0ud2l0aChzYW1wbGVfcHJpb3IgPSAib25seSIpLCBuc2FtcGxlcyA9IDIwMCkKCnN1bW1hcnkobS53aXRoKCkpCgpwcF9jaGVjayhtLndpdGgoKSwgbnNhbXBsZXMgPSAyMDApCgpsb28oCiAgbS53aXRoKCksCiAgbS53aXRoKCJoaWdoX2RlYnRfdmVyc2lvbiIpLAogIG0ud2l0aChjKCJoaWdoX2RlYnRfdmVyc2lvbiIsICJzY2VuYXJpbyIpKQopCiMjIEVuZChOb3QgcnVuKQpgYGAK