Below UCI dataset is a “Bank Marketing” campaign that contains records of calls made by a Portugese bank to its clients, including client and campaign attributes. Analysis is based on classfication methods to understand if a customer is going to open a deposit account or not.

Data Cleaning and Preparation

Load all relevant libraries.

library(readr)
library(ggplot2)
library(lattice)
library(plyr)
library(dplyr)
library(caret)
library(mlbench)
library(foreign)
library(ggplot2)
library(reshape)
library(scales)
library(e1071)
library(MASS)
library(klaR)
library(C50)
library(kernlab)
library(nnet)

Read the data set on bank clients. Here, analysis is based on the smaller dataset that represents randomly selected 10% of the entire dataset, so that computationally demanding algorithms (eg: SVM) can be performed faster.

bank <- read_delim("~/Documents/homework/ITM_6285/bank-additional.csv",";",escape_double = FALSE, trim_ws = TRUE)
bank <- subset(bank, select = -c(duration))

There are 20 attributes in the dataset. Since duration has a high correlation with the target variable, variable named ‘duration’ is removed from the dataset. Here is a breakdown of all 20 variables in the dataset along with variable data type. The target variable is y which has two values: ‘yes’ (customer opens a bank account) and ‘no’ (customer does not open an account).

To get an understanding of the data, lets visualize a few variables.

table(bank$y)

  no  yes 
3668  451 

The dataset contains 3668 ‘no’ responses and 451 ‘yes’ responses. Below is the distribution by occupation and age.

barplot(table(bank$job),col="red",ylab="No. of Clients",las=2,main="Job",cex.names = 0.8,cex.axis = 0.8)

boxplot(bank$age~bank$y, main=" Age",ylab="Age of Clients",xlab="Deposit A/C Open or Not")

Splitting Data for Testing and Training

Now the dataset of 4119 observations are splitted into training and test data. We use stratified sampling to split the data, so that distribution of the outcome within traning and testing datasets is preserved. We split the data with 75% (or 3090) of observations is used for training the model and 25% (or 1029) of observations is used to test the prediction outcome from the classifier model.

set.seed(123456)
TrainingDataIndex <- createDataPartition(bank$y, p=0.75, list = FALSE)
train <- bank[TrainingDataIndex,]
test <-bank[-TrainingDataIndex,]
prop.table(table(train$y))

       no       yes 
0.8902913 0.1097087 
nrow(train)
[1] 3090
prop.table(table(test$y))

       no       yes 
0.8911565 0.1088435 
nrow(test)
[1] 1029

Thus, stratified sampling has enabled to maintain the distribution with about 89% of clients have responded ‘no’ to opening a deposit in both testing and training data set.

1. Classification Methods

a) Decision Tree

Training the Model

After partitioning the data to train and test, use a 10 fold cross validation repeated 5 times to evaluate the model.

TrainingParameters <- trainControl(method = "cv", number = 10, repeats = 5)

Then create the decision tree using the C5.0 algorithm.

DecTreeModel <- train(y ~ ., data = train, 
                      method = "C5.0",
                      trControl= TrainingParameters,
                      na.action = na.omit)

Lets take a look.

DecTreeModel
C5.0 

3090 samples
  19 predictor
   2 classes: 'no', 'yes' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 2781, 2781, 2781, 2782, 2781, 2781, ... 
Resampling results across tuning parameters:

  model  winnow  trials  Accuracy   Kappa    
  rules  FALSE    1      0.9009644  0.2583583
  rules  FALSE   10      0.8954690  0.2759816
  rules  FALSE   20      0.8957927  0.2839006
  rules   TRUE    1      0.8974056  0.2271179
  rules   TRUE   10      0.8944908  0.2386011
  rules   TRUE   20      0.8944908  0.2458461
  tree   FALSE    1      0.9009644  0.2583583
  tree   FALSE   10      0.8961173  0.2716831
  tree   FALSE   20      0.8957927  0.2820709
  tree    TRUE    1      0.8974056  0.2271179
  tree    TRUE   10      0.8948145  0.2395735
  tree    TRUE   20      0.8951381  0.2431570

Accuracy was used to select the optimal model using  the largest value.
The final values used for the model were trials = 1, model = rules and winnow = FALSE. 
summary(DecTreeModel)

Call:
C5.0.default(x = structure(c(30, 39, 25, 38, 47, 32, 32, 41, 31, 35, 25, 36, 29, 27, 46, 45, 50, 39,
 = c("subset", "bands", "winnow", "noGlobalPruning", "CF", "minCases", "fuzzyThreshold",
 "sample", "earlyStopping", "label", "seed")))


C5.0 [Release 2.07 GPL Edition]     Thu Mar 16 20:18:59 2017
-------------------------------

Class specified by attribute `outcome'

Read 3090 cases (53 attributes) from undefined.data

Rules:

Rule 1: (2816/207, lift 1.0)
    nr.employed > 5023.5
    ->  class no  [0.926]

Rule 2: (2977/266, lift 1.0)
    poutcomesuccess <= 0
    ->  class no  [0.910]

Rule 3: (78/17, lift 7.1)
    poutcomesuccess > 0
    nr.employed <= 5023.5
    ->  class yes  [0.775]

Default class: no


Evaluation on training data (3090 cases):

            Rules     
      ----------------
        No      Errors

         3  295( 9.5%)   <<


       (a)   (b)    <-classified as
      ----  ----
      2734    17    (a): class no
       278    61    (b): class yes


    Attribute usage:

     98.87% poutcomesuccess
     93.66% nr.employed


Time: 0.0 secs

For instance, Rule 1 shows that when the number of employees in a quarter is greater than 5023, it was assigned the class ‘no’ (client does not want to open a bank account) 2816 times and out of 2816 times, the model incorrectly assigned ‘no’ 207 times.

Based on the training data confusion matrix, 9.5% of observations were assigned an incorrect class variable.

Testing the Model
DTPredictions <-predict(DecTreeModel, test, na.action = na.pass)
confusionMatrix(DTPredictions, test$y)
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  911  97
       yes   6  15
                                          
               Accuracy : 0.8999          
                 95% CI : (0.8799, 0.9176)
    No Information Rate : 0.8912          
    P-Value [Acc > NIR] : 0.1984          
                                          
                  Kappa : 0.198           
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.9935          
            Specificity : 0.1339          
         Pos Pred Value : 0.9038          
         Neg Pred Value : 0.7143          
             Prevalence : 0.8912          
         Detection Rate : 0.8853          
   Detection Prevalence : 0.9796          
      Balanced Accuracy : 0.5637          
                                          
       'Positive' Class : no              
                                          

Based on confusion matrix for test data, using the decision tree model we have correctly classified 911 + 15 = 926 observations and misclassified 6 + 97 = 103 representing a 90% accuracy.

b) Naive Bayes

Training the Model

The next machine learning method used to predict if a customer opens a bank account is Naive Bayes method. The Naive Bayes method assumes independece among each 19 variables, i.e. the algorithm assumes that attributes such as job and education are independent from each other in predicting whether a customer will open a bank account or not.

set.seed(100)
TrainingDataIndex <- createDataPartition(bank$y, p=0.75, list = FALSE)
train <- bank[TrainingDataIndex,]
test <-bank[-TrainingDataIndex,]
NBModel <- train(train[,-20], train$y, method = "nb",trControl= trainControl(method = "cv", number = 10, repeats = 5))
NBModel

After invoking the Naive Bayes method using training data set, lets feed test data to the model.

Testing the model

Below confusion matrix by class y shows that there is 89% accuracy in classification per Naive Bayes method.

NBPredictions <-predict(NBModel, test)
confusionMatrix(NBPredictions, test$y)
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  867  66
       yes  50  46
                                          
               Accuracy : 0.8873          
                 95% CI : (0.8663, 0.9059)
    No Information Rate : 0.8912          
    P-Value [Acc > NIR] : 0.6775          
                                          
                  Kappa : 0.38            
 Mcnemar's Test P-Value : 0.1637          
                                          
            Sensitivity : 0.9455          
            Specificity : 0.4107          
         Pos Pred Value : 0.9293          
         Neg Pred Value : 0.4792          
             Prevalence : 0.8912          
         Detection Rate : 0.8426          
   Detection Prevalence : 0.9067          
      Balanced Accuracy : 0.6781          
                                          
       'Positive' Class : no              
                                          

c) Suppor Vector Machines (SVM)

SVM is another classification method that can be used to predict if a client falls into either ‘yes’ or ‘no’ class.

Training the model

As before, create a prediction model using svmPoly method.

set.seed(120)
TrainingDataIndex <- createDataPartition(bank$y, p=0.75, list = FALSE)
train <- bank[TrainingDataIndex,]
test <-bank[-TrainingDataIndex,]
svm_model <- train(y~., data = train,
                   method = "svmPoly",
                   trControl= trainControl(method = "cv", number = 10, repeats = 5),
                   tuneGrid = data.frame(degree = 1,scale = 1,C = 1))
svm_model
Support Vector Machines with Polynomial Kernel 

3090 samples
  19 predictor
   2 classes: 'no', 'yes' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 2781, 2781, 2781, 2781, 2781, 2781, ... 
Resampling results:

  Accuracy   Kappa    
  0.9000019  0.2830744

Tuning parameter 'degree' was held constant at a value of 1
Tuning parameter 'scale' was held constant
 at a value of 1
Tuning parameter 'C' was held constant at a value of 1
 

After using polynomial kernal function to build a model, lets use test data to predict the accuracy of the model.

Testing the model
SVMPredictions <-predict(svm_model, test, na.action = na.pass)
confusionMatrix(SVMPredictions, test$y)
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  902  92
       yes  15  20
                                         
               Accuracy : 0.896          
                 95% CI : (0.8757, 0.914)
    No Information Rate : 0.8912         
    P-Value [Acc > NIR] : 0.33           
                                         
                  Kappa : 0.2323         
 Mcnemar's Test P-Value : 2.024e-13      
                                         
            Sensitivity : 0.9836         
            Specificity : 0.1786         
         Pos Pred Value : 0.9074         
         Neg Pred Value : 0.5714         
             Prevalence : 0.8912         
         Detection Rate : 0.8766         
   Detection Prevalence : 0.9660         
      Balanced Accuracy : 0.5811         
                                         
       'Positive' Class : no             
                                         

As evident, the SVM classfication method gives a 89.6% accuracy predicting only 15 instances of false positives.

d) Neural Network

Neural netowrks attempt to mimic the learning pattern of natural biological neural network. Lets use a neural network method to understand a customer’s decision to open a bank account.

Training the model
set.seed(80)
TrainingDataIndex <- createDataPartition(bank$y, p=0.75, list = FALSE)
train <- bank[TrainingDataIndex,]
test <-bank[-TrainingDataIndex,]
nnmodel <- train(train[,-20], train$y, method = "nnet",
                 trControl= trainControl(method = "cv", number = 10, repeats = 5))
nnmodel

After training the model using nnet method, use a confusion matrix to evaluate the performanc eof the model on test data.

Testing the model
nnetpredictions <-predict(nnmodel, test, na.action = na.pass)
confusionMatrix(nnetpredictions, test$y)
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  898  76
       yes  19  36
                                          
               Accuracy : 0.9077          
                 95% CI : (0.8883, 0.9247)
    No Information Rate : 0.8912          
    P-Value [Acc > NIR] : 0.04685         
                                          
                  Kappa : 0.3872          
 Mcnemar's Test P-Value : 9.166e-09       
                                          
            Sensitivity : 0.9793          
            Specificity : 0.3214          
         Pos Pred Value : 0.9220          
         Neg Pred Value : 0.6545          
             Prevalence : 0.8912          
         Detection Rate : 0.8727          
   Detection Prevalence : 0.9466          
      Balanced Accuracy : 0.6504          
                                          
       'Positive' Class : no              
                                          

Based on the confusion matrix, there are only 19 instances of false positives. The neural network has a 90.8% accuracy.

2. Model Evaluation

We created four models above to classify whether a cutomer would open a bank account or not. Lets build some key performance indicators to understand which model is the most successful in predicting the customer’s decision.

The typically used performance metrics are:

  1. precision: success rate in identifying whether a customer did not subscibe to the deposit account
  2. recall: proportion of clients correctly or incorrectly predicted to unsubscribe to an account

The classification goal is to predict whether or not customers will subscribe to a term deposit. Here the positive class is ‘no’ or that a customer does not subscribe to a deposit. Thus, it is important to choose a model with a low recall, i.e. the model that should contain a lower proportion of true positives (customers that did not subscribe to the deposit) out of total actual positives. If the bank aggressively determines those customers that do not subscribe to the bank account, the bank will lose some customers.

In order to illustrate recall and precision for each model, lets compute the weighted F-measure. The R output of the Confusion Matrix of each model already calculates recall and precision indicated by sensitivity and Pos Pred Value respectively. Thus, we can compute weighted F-measure (giving equal weights to reall and precision) as below. We collect sensitivity and Pos Pred Value from confusion matrix to compute F-measure for each model.

model = c("dec","nb","svm","nn")
recall = c(0.9935,0.9466,0.9836,0.9793)
precision = c(0.9038,0.9175,0.9074,0.9220)
fmeasure <- 2 * precision * recall / (precision + recall)
eval_table = data.frame(model,recall,precision,fmeasure) 
eval_table

Based on the above table, Naive Bayes method is the recommended classification method as it contains lowest recall. We do not want a model that aggressively classifies a customer response as ‘no’, we want more customers to open a bank account.

3. Effect of PCA on Classification Performance

Since the bank dataset on telephone calls contains multiple variables, we can perform a principal component analysis (PCA), a dimensionality reduction technique, to reduce some of the variables with less variance, such that we can improve the model performances by focusing only on those attributes with relatively high variance.

As before, we will partition the data to test and training and perform each classification method to predict whether or not a customer will open a bank account. The pca function in caret package in R is used to perform dimensionality reduction which will exclude all categorical variables in the bank dataset.

TrainingDataIndex <- createDataPartition(bank$y, p=0.75, list = FALSE)
trainingData <- bank[TrainingDataIndex,]
testData <- bank[-TrainingDataIndex,]
Decision Tree

The decision tree model uses PCA to predict the class variable with an accuracy of 89.3%. This is slightly lower than the accuracy produced without performing dimensionalty reduction (89.9%). However, this model DecTreeModel2 contains a higher precision, 91.3% compared to 90.4% of DTPredictions.

set.seed(30)
DecTreeModel2 <- train(trainingData[,-20], trainingData$y, 
                       method = "C5.0",
                       trControl= trainControl(method = "cv", number = 10),
                       preProcess = c("pca"),
                       na.action = na.omit)
DTPredictions2 <-predict(DecTreeModel, testData, na.action = na.pass)
confusionMatrix(DTPredictions2, testData$y)
Naive Bayes

With PCA, naive bayes method produces a higher accuracy of 88.6% compared to the accuracy produced with PCA, 87.7%, thus this model predict a higher true negative rate (customers identified as opening a bank account) compared to the model without PCA. This model produces the same recall in comparison to the naive bayes model without PCA. The specificity is significantly higher than that from without PCA (39% versus 30%). Specificity is instances of true negative (44) as a proportion of true negative and false positive (44 + 68). In the banking campaigns, we want to minimize false positives, i.e. identifying class variable as ‘no’ when a customer actually wants to a bank account.

NBPredictions2 <-predict(NBModel2, testData, na.action = na.pass)
confusionMatrix(NBPredictions2, testData$y)
Support Vector Machine

When using PCA with SVM polynomial model, the accuracy improved from 89.6% to 90.3%. However, the model using PCA produced higher false positives (the model predicted a ‘no’ when a customer subscibed to an account) and thus SVM using PCA produced a higher precision, 91.1% versus 90.7%.

set.seed(40)
SVModel2 <- train(y ~ ., data = trainingData,
                 method = "svmPoly",
                 preProcess = c("pca"),
                 trControl= trainControl(method = "cv", number = 10),
                 tuneGrid = data.frame(degree = 1,
                                       scale = 1,
                                       C = 1))
SVpredictions2 <-predict(SVModel2, testData, na.action = na.pass)
confusionMatrix(SVpredictions2, testData$y)
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  901  97
       yes  16  15
                                          
               Accuracy : 0.8902          
                 95% CI : (0.8695, 0.9086)
    No Information Rate : 0.8912          
    P-Value [Acc > NIR] : 0.5647          
                                          
                  Kappa : 0.1707          
 Mcnemar's Test P-Value : 5.241e-14       
                                          
            Sensitivity : 0.9826          
            Specificity : 0.1339          
         Pos Pred Value : 0.9028          
         Neg Pred Value : 0.4839          
             Prevalence : 0.8912          
         Detection Rate : 0.8756          
   Detection Prevalence : 0.9699          
      Balanced Accuracy : 0.5582          
                                          
       'Positive' Class : no              
                                          
Neural Networks

When using PCA for neural network, the model produces a significantly lower accuracy 86% compared with 91% from neural network model without PCA. However, this model with PCA contains lower number of false positives and as such neural network classification with PCA is recommended over the neural network without PCA.

set.seed(30)
NNModel2 <- train(trainingData[,-20], trainingData$y,
                method = "nnet",
                preProcess = c("pca"),
                trControl= trainControl(method = "cv", number = 10),
                tuneGrid = data.frame(size = 5,
                decay = 0))
NNpredictions2 <-predict(NNModel2, testData, na.action = na.pass)
confusionMatrix(NNpredictions2, testData$y)
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  917 112
       yes   0   0
                                          
               Accuracy : 0.8912          
                 95% CI : (0.8705, 0.9095)
    No Information Rate : 0.8912          
    P-Value [Acc > NIR] : 0.5251          
                                          
                  Kappa : 0               
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 1.0000          
            Specificity : 0.0000          
         Pos Pred Value : 0.8912          
         Neg Pred Value :    NaN          
             Prevalence : 0.8912          
         Detection Rate : 0.8912          
   Detection Prevalence : 1.0000          
      Balanced Accuracy : 0.5000          
                                          
       'Positive' Class : no              
                                          
LS0tCnRpdGxlOiAiV2lsbCBhIEN1c3RvbWVyIE9wZW4gYSBCYW5rIEFjY291bnQ/IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6CiAgICBmaWdfd2lkdGg6IDUKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCgpCZWxvdyBVQ0kgZGF0YXNldCBpcyBhICJCYW5rIE1hcmtldGluZyIgY2FtcGFpZ24gdGhhdCBjb250YWlucyByZWNvcmRzIG9mIGNhbGxzIG1hZGUgYnkgYSBQb3J0dWdlc2UgYmFuayB0byBpdHMgY2xpZW50cywgaW5jbHVkaW5nIGNsaWVudCBhbmQgY2FtcGFpZ24gYXR0cmlidXRlcy4gQW5hbHlzaXMgaXMgYmFzZWQgb24gY2xhc3NmaWNhdGlvbiBtZXRob2RzIHRvIHVuZGVyc3RhbmQgaWYgYSBjdXN0b21lciBpcyBnb2luZyB0byBvcGVuIGEgZGVwb3NpdCBhY2NvdW50IG9yIG5vdC4KCiMjIyBEYXRhIENsZWFuaW5nIGFuZCBQcmVwYXJhdGlvbgpMb2FkIGFsbCByZWxldmFudCBsaWJyYXJpZXMuCgpgYGB7ciwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD01LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobGF0dGljZSkKbGlicmFyeShwbHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KG1sYmVuY2gpCmxpYnJhcnkoZm9yZWlnbikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHJlc2hhcGUpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KGUxMDcxKQpsaWJyYXJ5KE1BU1MpCmxpYnJhcnkoa2xhUikKbGlicmFyeShDNTApCmxpYnJhcnkoa2VybmxhYikKbGlicmFyeShubmV0KQpgYGAKCgpSZWFkIHRoZSBkYXRhIHNldCBvbiBiYW5rIGNsaWVudHMuIEhlcmUsIGFuYWx5c2lzIGlzIGJhc2VkIG9uIHRoZSBzbWFsbGVyIGRhdGFzZXQgdGhhdCByZXByZXNlbnRzIHJhbmRvbWx5IHNlbGVjdGVkIDEwJSBvZiB0aGUgZW50aXJlIGRhdGFzZXQsIHNvIHRoYXQgY29tcHV0YXRpb25hbGx5IGRlbWFuZGluZyBhbGdvcml0aG1zIChlZzogU1ZNKSBjYW4gYmUgcGVyZm9ybWVkIGZhc3Rlci4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpiYW5rIDwtIHJlYWRfZGVsaW0oIn4vRG9jdW1lbnRzL2hvbWV3b3JrL0lUTV82Mjg1L2JhbmstYWRkaXRpb25hbC5jc3YiLCI7Iixlc2NhcGVfZG91YmxlID0gRkFMU0UsIHRyaW1fd3MgPSBUUlVFKQpiYW5rIDwtIHN1YnNldChiYW5rLCBzZWxlY3QgPSAtYyhkdXJhdGlvbikpCmBgYApUaGVyZSBhcmUgMjAgYXR0cmlidXRlcyBpbiB0aGUgZGF0YXNldC4gU2luY2UgZHVyYXRpb24gaGFzIGEgaGlnaCBjb3JyZWxhdGlvbiB3aXRoIHRoZSB0YXJnZXQgdmFyaWFibGUsIHZhcmlhYmxlIG5hbWVkICdkdXJhdGlvbicgaXMgcmVtb3ZlZCBmcm9tIHRoZSBkYXRhc2V0LiAKSGVyZSBpcyBhIGJyZWFrZG93biBvZiBhbGwgMjAgdmFyaWFibGVzIGluIHRoZSBkYXRhc2V0IGFsb25nIHdpdGggdmFyaWFibGUgZGF0YSB0eXBlLgpUaGUgdGFyZ2V0IHZhcmlhYmxlIGlzIHkgd2hpY2ggaGFzIHR3byB2YWx1ZXM6ICd5ZXMnIChjdXN0b21lciBvcGVucyBhIGJhbmsgYWNjb3VudCkgYW5kICdubycgKGN1c3RvbWVyIGRvZXMgbm90IG9wZW4gYW4gYWNjb3VudCkuCgpUbyBnZXQgYW4gdW5kZXJzdGFuZGluZyBvZiB0aGUgZGF0YSwgbGV0cyB2aXN1YWxpemUgYSBmZXcgdmFyaWFibGVzLgpgYGB7ciwgd2FybmluZz1GQUxTRX0KdGFibGUoYmFuayR5KQpgYGAKVGhlIGRhdGFzZXQgY29udGFpbnMgMzY2OCAnbm8nIHJlc3BvbnNlcyBhbmQgNDUxICd5ZXMnIHJlc3BvbnNlcy4KQmVsb3cgaXMgdGhlIGRpc3RyaWJ1dGlvbiBieSBvY2N1cGF0aW9uIGFuZCBhZ2UuCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02fQpiYXJwbG90KHRhYmxlKGJhbmskam9iKSxjb2w9InJlZCIseWxhYj0iTm8uIG9mIENsaWVudHMiLGxhcz0yLG1haW49IkpvYiIsY2V4Lm5hbWVzID0gMC44LGNleC5heGlzID0gMC44KQpgYGAKYGBge3IsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTV9CmJveHBsb3QoYmFuayRhZ2V+YmFuayR5LCBtYWluPSIgQWdlIix5bGFiPSJBZ2Ugb2YgQ2xpZW50cyIseGxhYj0iRGVwb3NpdCBBL0MgT3BlbiBvciBOb3QiKQpgYGAKCiMjIyBTcGxpdHRpbmcgRGF0YSBmb3IgVGVzdGluZyBhbmQgVHJhaW5pbmcKCk5vdyB0aGUgZGF0YXNldCBvZiA0MTE5IG9ic2VydmF0aW9ucyBhcmUgc3BsaXR0ZWQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhLiAKV2UgdXNlIHN0cmF0aWZpZWQgc2FtcGxpbmcgdG8gc3BsaXQgdGhlIGRhdGEsIHNvIHRoYXQgZGlzdHJpYnV0aW9uIG9mIHRoZSBvdXRjb21lIHdpdGhpbiB0cmFuaW5nIGFuZCB0ZXN0aW5nIGRhdGFzZXRzIGlzIHByZXNlcnZlZC4gV2Ugc3BsaXQgdGhlIGRhdGEgd2l0aCA3NSUgKG9yIDMwOTApIG9mIG9ic2VydmF0aW9ucyBpcyB1c2VkIGZvciB0cmFpbmluZyB0aGUgbW9kZWwgYW5kIDI1JSAob3IgMTAyOSkgb2Ygb2JzZXJ2YXRpb25zIGlzIHVzZWQgdG8gdGVzdCB0aGUgcHJlZGljdGlvbiBvdXRjb21lIGZyb20gdGhlIGNsYXNzaWZpZXIgbW9kZWwuCmBgYHtyfQpzZXQuc2VlZCgxMjM0NTYpClRyYWluaW5nRGF0YUluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oYmFuayR5LCBwPTAuNzUsIGxpc3QgPSBGQUxTRSkKdHJhaW4gPC0gYmFua1tUcmFpbmluZ0RhdGFJbmRleCxdCnRlc3QgPC1iYW5rWy1UcmFpbmluZ0RhdGFJbmRleCxdCnByb3AudGFibGUodGFibGUodHJhaW4keSkpCm5yb3codHJhaW4pCnByb3AudGFibGUodGFibGUodGVzdCR5KSkKbnJvdyh0ZXN0KQpgYGAKClRodXMsIHN0cmF0aWZpZWQgc2FtcGxpbmcgaGFzIGVuYWJsZWQgdG8gbWFpbnRhaW4gdGhlIGRpc3RyaWJ1dGlvbiB3aXRoIGFib3V0IDg5JSBvZiBjbGllbnRzIGhhdmUgcmVzcG9uZGVkICdubycgdG8gb3BlbmluZyBhIGRlcG9zaXQgaW4gYm90aCB0ZXN0aW5nIGFuZCB0cmFpbmluZyBkYXRhIHNldC4KCiMjIyAxLiBDbGFzc2lmaWNhdGlvbiBNZXRob2RzCiMjIyMgYSkgRGVjaXNpb24gVHJlZQojIyMjIyBUcmFpbmluZyB0aGUgTW9kZWwKQWZ0ZXIgcGFydGl0aW9uaW5nIHRoZSBkYXRhIHRvIHRyYWluIGFuZCB0ZXN0LCB1c2UgYSAxMCBmb2xkIGNyb3NzIHZhbGlkYXRpb24gcmVwZWF0ZWQgNSB0aW1lcyB0byBldmFsdWF0ZSB0aGUgbW9kZWwuCgpgYGB7cn0KVHJhaW5pbmdQYXJhbWV0ZXJzIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCwgcmVwZWF0cyA9IDUpCmBgYApUaGVuIGNyZWF0ZSB0aGUgZGVjaXNpb24gdHJlZSB1c2luZyB0aGUgQzUuMCBhbGdvcml0aG0uCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KRGVjVHJlZU1vZGVsIDwtIHRyYWluKHkgfiAuLCBkYXRhID0gdHJhaW4sIAogICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gIkM1LjAiLAogICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sPSBUcmFpbmluZ1BhcmFtZXRlcnMsCiAgICAgICAgICAgICAgICAgICAgICBuYS5hY3Rpb24gPSBuYS5vbWl0KQpgYGAKCkxldHMgdGFrZSBhIGxvb2suCmBgYHtyfQpEZWNUcmVlTW9kZWwKYGBgCgpgYGB7cn0Kc3VtbWFyeShEZWNUcmVlTW9kZWwpCmBgYApGb3IgaW5zdGFuY2UsIFJ1bGUgMSBzaG93cyB0aGF0IHdoZW4gdGhlIG51bWJlciBvZiBlbXBsb3llZXMgaW4gYSBxdWFydGVyIGlzIGdyZWF0ZXIgdGhhbiA1MDIzLCBpdCB3YXMgYXNzaWduZWQgdGhlIGNsYXNzICdubycgKGNsaWVudCBkb2VzIG5vdCB3YW50IHRvIG9wZW4gYSBiYW5rIGFjY291bnQpIDI4MTYgdGltZXMgYW5kIG91dCBvZiAyODE2IHRpbWVzLCB0aGUgbW9kZWwgaW5jb3JyZWN0bHkgYXNzaWduZWQgJ25vJyAyMDcgdGltZXMuCgpCYXNlZCBvbiB0aGUgdHJhaW5pbmcgZGF0YSBjb25mdXNpb24gbWF0cml4LCA5LjUlIG9mIG9ic2VydmF0aW9ucyB3ZXJlIGFzc2lnbmVkIGFuIGluY29ycmVjdCBjbGFzcyB2YXJpYWJsZS4KCiMjIyMjIFRlc3RpbmcgdGhlIE1vZGVsCgpgYGB7cn0KRFRQcmVkaWN0aW9ucyA8LXByZWRpY3QoRGVjVHJlZU1vZGVsLCB0ZXN0LCBuYS5hY3Rpb24gPSBuYS5wYXNzKQpjb25mdXNpb25NYXRyaXgoRFRQcmVkaWN0aW9ucywgdGVzdCR5KQpgYGAKQmFzZWQgb24gY29uZnVzaW9uIG1hdHJpeCBmb3IgdGVzdCBkYXRhLCB1c2luZyB0aGUgZGVjaXNpb24gdHJlZSBtb2RlbCB3ZSBoYXZlIGNvcnJlY3RseSBjbGFzc2lmaWVkIDkxMSArIDE1ID0gOTI2IG9ic2VydmF0aW9ucyBhbmQgbWlzY2xhc3NpZmllZCA2ICsgOTcgPSAxMDMgcmVwcmVzZW50aW5nIGEgOTAlIGFjY3VyYWN5LgoKIyMjIyBiKSBOYWl2ZSBCYXllcwojIyMjIyBUcmFpbmluZyB0aGUgTW9kZWwKClRoZSBuZXh0IG1hY2hpbmUgbGVhcm5pbmcgbWV0aG9kIHVzZWQgdG8gcHJlZGljdCBpZiBhIGN1c3RvbWVyIG9wZW5zIGEgYmFuayBhY2NvdW50IGlzIE5haXZlIEJheWVzIG1ldGhvZC4KVGhlIE5haXZlIEJheWVzIG1ldGhvZCBhc3N1bWVzIGluZGVwZW5kZWNlIGFtb25nIGVhY2ggMTkgdmFyaWFibGVzLCBpLmUuIHRoZSBhbGdvcml0aG0gYXNzdW1lcyB0aGF0IGF0dHJpYnV0ZXMgc3VjaCBhcyBqb2IgYW5kIGVkdWNhdGlvbiBhcmUgaW5kZXBlbmRlbnQgZnJvbSBlYWNoIG90aGVyIGluIHByZWRpY3Rpbmcgd2hldGhlciBhIGN1c3RvbWVyIHdpbGwgb3BlbiBhIGJhbmsgYWNjb3VudCBvciBub3QuCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0iaGlkZSJ9CnNldC5zZWVkKDEwMCkKVHJhaW5pbmdEYXRhSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihiYW5rJHksIHA9MC43NSwgbGlzdCA9IEZBTFNFKQp0cmFpbiA8LSBiYW5rW1RyYWluaW5nRGF0YUluZGV4LF0KdGVzdCA8LWJhbmtbLVRyYWluaW5nRGF0YUluZGV4LF0KTkJNb2RlbCA8LSB0cmFpbih0cmFpblssLTIwXSwgdHJhaW4keSwgbWV0aG9kID0gIm5iIix0ckNvbnRyb2w9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCwgcmVwZWF0cyA9IDUpKQpOQk1vZGVsCmBgYAoKQWZ0ZXIgaW52b2tpbmcgdGhlIE5haXZlIEJheWVzIG1ldGhvZCB1c2luZyB0cmFpbmluZyBkYXRhIHNldCwgbGV0cyBmZWVkIHRlc3QgZGF0YSB0byB0aGUgbW9kZWwuCgojIyMjIyBUZXN0aW5nIHRoZSBtb2RlbAoKQmVsb3cgY29uZnVzaW9uIG1hdHJpeCBieSBjbGFzcyB5IHNob3dzIHRoYXQgdGhlcmUgaXMgODklIGFjY3VyYWN5IGluIGNsYXNzaWZpY2F0aW9uIHBlciBOYWl2ZSBCYXllcyBtZXRob2QuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KTkJQcmVkaWN0aW9ucyA8LXByZWRpY3QoTkJNb2RlbCwgdGVzdCkKY29uZnVzaW9uTWF0cml4KE5CUHJlZGljdGlvbnMsIHRlc3QkeSkKYGBgCgojIyMjIGMpIFN1cHBvciBWZWN0b3IgTWFjaGluZXMgKFNWTSkKU1ZNIGlzIGFub3RoZXIgY2xhc3NpZmljYXRpb24gbWV0aG9kIHRoYXQgY2FuIGJlIHVzZWQgdG8gcHJlZGljdCBpZiBhIGNsaWVudCBmYWxscyBpbnRvIGVpdGhlciAneWVzJyBvciAnbm8nIGNsYXNzLgoKIyMjIyMgVHJhaW5pbmcgdGhlIG1vZGVsCkFzIGJlZm9yZSwgY3JlYXRlIGEgcHJlZGljdGlvbiBtb2RlbCB1c2luZyBzdm1Qb2x5IG1ldGhvZC4KYGBge3IsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTQsIHdhcm5pbmc9RkFMU0V9CnNldC5zZWVkKDEyMCkKVHJhaW5pbmdEYXRhSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihiYW5rJHksIHA9MC43NSwgbGlzdCA9IEZBTFNFKQp0cmFpbiA8LSBiYW5rW1RyYWluaW5nRGF0YUluZGV4LF0KdGVzdCA8LWJhbmtbLVRyYWluaW5nRGF0YUluZGV4LF0Kc3ZtX21vZGVsIDwtIHRyYWluKHl+LiwgZGF0YSA9IHRyYWluLAogICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVBvbHkiLAogICAgICAgICAgICAgICAgICAgdHJDb250cm9sPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTAsIHJlcGVhdHMgPSA1KSwKICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShkZWdyZWUgPSAxLHNjYWxlID0gMSxDID0gMSkpCnN2bV9tb2RlbApgYGAKIApBZnRlciB1c2luZyBwb2x5bm9taWFsIGtlcm5hbCBmdW5jdGlvbiB0byBidWlsZCBhIG1vZGVsLCBsZXRzIHVzZSB0ZXN0IGRhdGEgdG8gcHJlZGljdCB0aGUgYWNjdXJhY3kgb2YgdGhlIG1vZGVsLgogCiMjIyMjIFRlc3RpbmcgdGhlIG1vZGVsCgpgYGB7ciwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KU1ZNUHJlZGljdGlvbnMgPC1wcmVkaWN0KHN2bV9tb2RlbCwgdGVzdCwgbmEuYWN0aW9uID0gbmEucGFzcykKY29uZnVzaW9uTWF0cml4KFNWTVByZWRpY3Rpb25zLCB0ZXN0JHkpCmBgYAoKQXMgZXZpZGVudCwgdGhlIFNWTSBjbGFzc2ZpY2F0aW9uIG1ldGhvZCBnaXZlcyBhIDg5LjYlIGFjY3VyYWN5IHByZWRpY3Rpbmcgb25seSAxNSBpbnN0YW5jZXMgb2YgZmFsc2UgcG9zaXRpdmVzLgoKIyMjIyBkKSBOZXVyYWwgTmV0d29yawoKTmV1cmFsIG5ldG93cmtzIGF0dGVtcHQgdG8gbWltaWMgdGhlIGxlYXJuaW5nIHBhdHRlcm4gb2YgbmF0dXJhbCBiaW9sb2dpY2FsIG5ldXJhbCBuZXR3b3JrLiBMZXRzIHVzZSBhIG5ldXJhbCBuZXR3b3JrIG1ldGhvZCB0byB1bmRlcnN0YW5kIGEgY3VzdG9tZXIncyBkZWNpc2lvbiB0byBvcGVuIGEgYmFuayBhY2NvdW50LgoKIyMjIyMgVHJhaW5pbmcgdGhlIG1vZGVsCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00LCB3YXJuaW5nPUZBTFNFLCBldmFsPUZBTFNFfQpzZXQuc2VlZCg4MCkKVHJhaW5pbmdEYXRhSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihiYW5rJHksIHA9MC43NSwgbGlzdCA9IEZBTFNFKQp0cmFpbiA8LSBiYW5rW1RyYWluaW5nRGF0YUluZGV4LF0KdGVzdCA8LWJhbmtbLVRyYWluaW5nRGF0YUluZGV4LF0Kbm5tb2RlbCA8LSB0cmFpbih0cmFpblssLTIwXSwgdHJhaW4keSwgbWV0aG9kID0gIm5uZXQiLAogICAgICAgICAgICAgICAgIHRyQ29udHJvbD0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzID0gNSkpCm5ubW9kZWwKYGBgCgpBZnRlciB0cmFpbmluZyB0aGUgbW9kZWwgdXNpbmcgbm5ldCBtZXRob2QsIHVzZSBhIGNvbmZ1c2lvbiBtYXRyaXggdG8gZXZhbHVhdGUgdGhlIHBlcmZvcm1hbmMgZW9mIHRoZSBtb2RlbCBvbiB0ZXN0IGRhdGEuCgojIyMjIyBUZXN0aW5nIHRoZSBtb2RlbAoKYGBge3IsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTQsIHdhcm5pbmc9RkFMU0V9Cm5uZXRwcmVkaWN0aW9ucyA8LXByZWRpY3Qobm5tb2RlbCwgdGVzdCwgbmEuYWN0aW9uID0gbmEucGFzcykKY29uZnVzaW9uTWF0cml4KG5uZXRwcmVkaWN0aW9ucywgdGVzdCR5KQpgYGAKCkJhc2VkIG9uIHRoZSBjb25mdXNpb24gbWF0cml4LCB0aGVyZSBhcmUgb25seSAxOSBpbnN0YW5jZXMgb2YgZmFsc2UgcG9zaXRpdmVzLiBUaGUgbmV1cmFsIG5ldHdvcmsgaGFzIGEgOTAuOCUgYWNjdXJhY3kuIAoKIyMjIDIuIE1vZGVsIEV2YWx1YXRpb24KCldlIGNyZWF0ZWQgZm91ciBtb2RlbHMgYWJvdmUgdG8gY2xhc3NpZnkgd2hldGhlciBhIGN1dG9tZXIgd291bGQgb3BlbiBhIGJhbmsgYWNjb3VudCBvciBub3QuIExldHMgYnVpbGQgc29tZSBrZXkgcGVyZm9ybWFuY2UgaW5kaWNhdG9ycyB0byB1bmRlcnN0YW5kIHdoaWNoIG1vZGVsIGlzIHRoZSBtb3N0IHN1Y2Nlc3NmdWwgaW4gcHJlZGljdGluZyB0aGUgY3VzdG9tZXIncyBkZWNpc2lvbi4KClRoZSB0eXBpY2FsbHkgdXNlZCBwZXJmb3JtYW5jZSBtZXRyaWNzIGFyZToKCmEpIHByZWNpc2lvbjogc3VjY2VzcyByYXRlIGluIGlkZW50aWZ5aW5nIHdoZXRoZXIgYSBjdXN0b21lciBkaWQgbm90IHN1YnNjaWJlIHRvIHRoZSBkZXBvc2l0IGFjY291bnQKYykgcmVjYWxsOiBwcm9wb3J0aW9uIG9mIGNsaWVudHMgY29ycmVjdGx5IG9yIGluY29ycmVjdGx5IHByZWRpY3RlZCB0byB1bnN1YnNjcmliZSB0byBhbiBhY2NvdW50CgoKVGhlIGNsYXNzaWZpY2F0aW9uIGdvYWwgaXMgdG8gcHJlZGljdCB3aGV0aGVyIG9yIG5vdCBjdXN0b21lcnMgd2lsbCBzdWJzY3JpYmUgdG8gYSB0ZXJtIGRlcG9zaXQuIEhlcmUgdGhlIHBvc2l0aXZlIGNsYXNzIGlzICdubycgb3IgdGhhdCBhIGN1c3RvbWVyIGRvZXMgbm90IHN1YnNjcmliZSB0byBhIGRlcG9zaXQuIFRodXMsIGl0IGlzIGltcG9ydGFudCB0byBjaG9vc2UgYSBtb2RlbCB3aXRoIGEgbG93IF9yZWNhbGxfLCBpLmUuIHRoZSBtb2RlbCB0aGF0IHNob3VsZCBjb250YWluIGEgbG93ZXIgcHJvcG9ydGlvbiBvZiB0cnVlIHBvc2l0aXZlcyAoY3VzdG9tZXJzIHRoYXQgZGlkIG5vdCBzdWJzY3JpYmUgdG8gdGhlIGRlcG9zaXQpIG91dCBvZiB0b3RhbCBhY3R1YWwgcG9zaXRpdmVzLiBJZiB0aGUgYmFuayBhZ2dyZXNzaXZlbHkgZGV0ZXJtaW5lcyB0aG9zZSBjdXN0b21lcnMgdGhhdCBkbyBub3Qgc3Vic2NyaWJlIHRvIHRoZSBiYW5rIGFjY291bnQsIHRoZSBiYW5rIHdpbGwgbG9zZSBzb21lIGN1c3RvbWVycy4gCgpJbiBvcmRlciB0byBpbGx1c3RyYXRlIHJlY2FsbCBhbmQgcHJlY2lzaW9uIGZvciBlYWNoIG1vZGVsLCBsZXRzIGNvbXB1dGUgdGhlIHdlaWdodGVkIEYtbWVhc3VyZS4gVGhlIFIgb3V0cHV0IG9mIHRoZSBDb25mdXNpb24gTWF0cml4IG9mIGVhY2ggbW9kZWwgYWxyZWFkeSBjYWxjdWxhdGVzIHJlY2FsbCBhbmQgcHJlY2lzaW9uIGluZGljYXRlZCBieSBfc2Vuc2l0aXZpdHlfIGFuZCAgX1BvcyBQcmVkIFZhbHVlXyByZXNwZWN0aXZlbHkuClRodXMsIHdlIGNhbiBjb21wdXRlIHdlaWdodGVkIEYtbWVhc3VyZSAoZ2l2aW5nIGVxdWFsIHdlaWdodHMgdG8gcmVhbGwgYW5kIHByZWNpc2lvbikgYXMgYmVsb3cuIFdlIGNvbGxlY3QgX3NlbnNpdGl2aXR5XyBhbmQgX1BvcyBQcmVkIFZhbHVlXyBmcm9tIGNvbmZ1c2lvbiBtYXRyaXggdG8gY29tcHV0ZSBGLW1lYXN1cmUgZm9yIGVhY2ggbW9kZWwuCgpgYGB7cn0KbW9kZWwgPSBjKCJkZWMiLCJuYiIsInN2bSIsIm5uIikKcmVjYWxsID0gYygwLjk5MzUsMC45NDY2LDAuOTgzNiwwLjk3OTMpCnByZWNpc2lvbiA9IGMoMC45MDM4LDAuOTE3NSwwLjkwNzQsMC45MjIwKQpmbWVhc3VyZSA8LSAyICogcHJlY2lzaW9uICogcmVjYWxsIC8gKHByZWNpc2lvbiArIHJlY2FsbCkKZXZhbF90YWJsZSA9IGRhdGEuZnJhbWUobW9kZWwscmVjYWxsLHByZWNpc2lvbixmbWVhc3VyZSkgCmV2YWxfdGFibGUKYGBgCgpCYXNlZCBvbiB0aGUgYWJvdmUgdGFibGUsIE5haXZlIEJheWVzIG1ldGhvZCBpcyB0aGUgcmVjb21tZW5kZWQgY2xhc3NpZmljYXRpb24gbWV0aG9kIGFzIGl0IGNvbnRhaW5zIGxvd2VzdCByZWNhbGwuIFdlIGRvIG5vdCB3YW50IGEgbW9kZWwgdGhhdCBhZ2dyZXNzaXZlbHkgY2xhc3NpZmllcyBhIGN1c3RvbWVyIHJlc3BvbnNlIGFzICdubycsIHdlIHdhbnQgbW9yZSBjdXN0b21lcnMgdG8gb3BlbiBhIGJhbmsgYWNjb3VudC4KCiMjIyAzLiBFZmZlY3Qgb2YgUENBIG9uIENsYXNzaWZpY2F0aW9uIFBlcmZvcm1hbmNlCgpTaW5jZSB0aGUgYmFuayBkYXRhc2V0IG9uIHRlbGVwaG9uZSBjYWxscyBjb250YWlucyBtdWx0aXBsZSB2YXJpYWJsZXMsIHdlIGNhbiBwZXJmb3JtIGEgcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyAoUENBKSwgYSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlLCB0byByZWR1Y2Ugc29tZSBvZiB0aGUgdmFyaWFibGVzIHdpdGggbGVzcyB2YXJpYW5jZSwgc3VjaCB0aGF0IHdlIGNhbiBpbXByb3ZlIHRoZSBtb2RlbCBwZXJmb3JtYW5jZXMgYnkgZm9jdXNpbmcgb25seSBvbiB0aG9zZSBhdHRyaWJ1dGVzIHdpdGggcmVsYXRpdmVseSBoaWdoIHZhcmlhbmNlLgoKQXMgYmVmb3JlLCB3ZSB3aWxsIHBhcnRpdGlvbiB0aGUgZGF0YSB0byB0ZXN0IGFuZCB0cmFpbmluZyBhbmQgcGVyZm9ybSBlYWNoIGNsYXNzaWZpY2F0aW9uIG1ldGhvZCB0byBwcmVkaWN0IHdoZXRoZXIgb3Igbm90IGEgY3VzdG9tZXIgd2lsbCBvcGVuIGEgYmFuayBhY2NvdW50LiBUaGUgcGNhIGZ1bmN0aW9uIGluIF9jYXJldF8gcGFja2FnZSBpbiBSIGlzIHVzZWQgdG8gcGVyZm9ybSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gd2hpY2ggd2lsbCBleGNsdWRlIGFsbCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW4gdGhlIGJhbmsgZGF0YXNldC4KCmBgYHtyfQpUcmFpbmluZ0RhdGFJbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGJhbmskeSwgcD0wLjc1LCBsaXN0ID0gRkFMU0UpCnRyYWluaW5nRGF0YSA8LSBiYW5rW1RyYWluaW5nRGF0YUluZGV4LF0KdGVzdERhdGEgPC0gYmFua1stVHJhaW5pbmdEYXRhSW5kZXgsXQpgYGAKCiMjIyMjIERlY2lzaW9uIFRyZWUgCgpUaGUgZGVjaXNpb24gdHJlZSBtb2RlbCB1c2VzIFBDQSB0byBwcmVkaWN0IHRoZSBjbGFzcyB2YXJpYWJsZSB3aXRoIGFuIGFjY3VyYWN5IG9mIDg5LjMlLiBUaGlzIGlzIHNsaWdodGx5IGxvd2VyIHRoYW4gdGhlIGFjY3VyYWN5IHByb2R1Y2VkIHdpdGhvdXQgcGVyZm9ybWluZyBkaW1lbnNpb25hbHR5IHJlZHVjdGlvbiAoODkuOSUpLiBIb3dldmVyLCB0aGlzIG1vZGVsIF9fRGVjVHJlZU1vZGVsMl9fIGNvbnRhaW5zIGEgaGlnaGVyIHByZWNpc2lvbiwgOTEuMyUgY29tcGFyZWQgdG8gOTAuNCUgb2YgX19EVFByZWRpY3Rpb25zX18uCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNldC5zZWVkKDMwKQpEZWNUcmVlTW9kZWwyIDwtIHRyYWluKHRyYWluaW5nRGF0YVssLTIwXSwgdHJhaW5pbmdEYXRhJHksIAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJDNS4wIiwKICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2w9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCksCiAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoInBjYSIpLAogICAgICAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbiA9IG5hLm9taXQpCkRUUHJlZGljdGlvbnMyIDwtcHJlZGljdChEZWNUcmVlTW9kZWwsIHRlc3REYXRhLCBuYS5hY3Rpb24gPSBuYS5wYXNzKQpjb25mdXNpb25NYXRyaXgoRFRQcmVkaWN0aW9uczIsIHRlc3REYXRhJHkpCmBgYAoKIyMjIyMgTmFpdmUgQmF5ZXMKCldpdGggUENBLCBuYWl2ZSBiYXllcyBtZXRob2QgcHJvZHVjZXMgYSBoaWdoZXIgYWNjdXJhY3kgb2YgODguNiUgY29tcGFyZWQgdG8gdGhlIGFjY3VyYWN5IHByb2R1Y2VkIHdpdGggUENBLCA4Ny43JSwgdGh1cyB0aGlzIG1vZGVsIHByZWRpY3QgYSBoaWdoZXIgdHJ1ZSBuZWdhdGl2ZSByYXRlIChjdXN0b21lcnMgaWRlbnRpZmllZCBhcyBvcGVuaW5nIGEgYmFuayBhY2NvdW50KSBjb21wYXJlZCB0byB0aGUgbW9kZWwgd2l0aG91dCBQQ0EuIFRoaXMgbW9kZWwgcHJvZHVjZXMgdGhlIHNhbWUgcmVjYWxsIGluIGNvbXBhcmlzb24gdG8gdGhlIG5haXZlIGJheWVzIG1vZGVsIHdpdGhvdXQgUENBLiBUaGUgc3BlY2lmaWNpdHkgaXMgc2lnbmlmaWNhbnRseSBoaWdoZXIgdGhhbiB0aGF0IGZyb20gd2l0aG91dCBQQ0EgKDM5JSB2ZXJzdXMgMzAlKS4gU3BlY2lmaWNpdHkgaXMgaW5zdGFuY2VzIG9mIHRydWUgbmVnYXRpdmUgKDQ0KSBhcyBhIHByb3BvcnRpb24gb2YgdHJ1ZSBuZWdhdGl2ZSBhbmQgZmFsc2UgcG9zaXRpdmUgKDQ0ICsgNjgpLiBJbiB0aGUgYmFua2luZyBjYW1wYWlnbnMsIHdlIHdhbnQgdG8gbWluaW1pemUgZmFsc2UgcG9zaXRpdmVzLCBpLmUuIGlkZW50aWZ5aW5nIGNsYXNzIHZhcmlhYmxlIGFzICdubycgd2hlbiBhIGN1c3RvbWVyIGFjdHVhbGx5IHdhbnRzIHRvIGEgYmFuayBhY2NvdW50LgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9ImhpZGUifQpzZXQuc2VlZCgyMCkKTkJNb2RlbDIgPC0gdHJhaW4odHJhaW5pbmdEYXRhWywtMjBdLCB0cmFpbmluZ0RhdGEkeSwgCiAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gIm5iIiwKICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2w9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCksCiAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoInBjYSIpLAogICAgICAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbiA9IG5hLm9taXQpCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Ck5CUHJlZGljdGlvbnMyIDwtcHJlZGljdChOQk1vZGVsMiwgdGVzdERhdGEsIG5hLmFjdGlvbiA9IG5hLnBhc3MpCmNvbmZ1c2lvbk1hdHJpeChOQlByZWRpY3Rpb25zMiwgdGVzdERhdGEkeSkKYGBgCgojIyMjIyBTdXBwb3J0IFZlY3RvciBNYWNoaW5lCgpXaGVuIHVzaW5nIFBDQSB3aXRoIFNWTSBwb2x5bm9taWFsIG1vZGVsLCB0aGUgYWNjdXJhY3kgaW1wcm92ZWQgZnJvbSA4OS42JSB0byA5MC4zJS4gSG93ZXZlciwgdGhlIG1vZGVsIHVzaW5nIFBDQSBwcm9kdWNlZCBoaWdoZXIgZmFsc2UgcG9zaXRpdmVzICh0aGUgbW9kZWwgcHJlZGljdGVkIGEgJ25vJyB3aGVuIGEgY3VzdG9tZXIgc3Vic2NpYmVkIHRvIGFuIGFjY291bnQpIGFuZCB0aHVzIFNWTSB1c2luZyBQQ0EgcHJvZHVjZWQgYSBoaWdoZXIgcHJlY2lzaW9uLCA5MS4xJSB2ZXJzdXMgOTAuNyUuICAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzZXQuc2VlZCg0MCkKU1ZNb2RlbDIgPC0gdHJhaW4oeSB+IC4sIGRhdGEgPSB0cmFpbmluZ0RhdGEsCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVBvbHkiLAogICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJwY2EiKSwKICAgICAgICAgICAgICAgICB0ckNvbnRyb2w9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCksCiAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBkYXRhLmZyYW1lKGRlZ3JlZSA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQyA9IDEpKQpTVnByZWRpY3Rpb25zMiA8LXByZWRpY3QoU1ZNb2RlbDIsIHRlc3REYXRhLCBuYS5hY3Rpb24gPSBuYS5wYXNzKQpjb25mdXNpb25NYXRyaXgoU1ZwcmVkaWN0aW9uczIsIHRlc3REYXRhJHkpCmBgYAoKCiMjIyMjIE5ldXJhbCBOZXR3b3JrcwoKV2hlbiB1c2luZyBQQ0EgZm9yIG5ldXJhbCBuZXR3b3JrLCB0aGUgbW9kZWwgcHJvZHVjZXMgYSBzaWduaWZpY2FudGx5IGxvd2VyIGFjY3VyYWN5IDg2JSBjb21wYXJlZCB3aXRoIDkxJSBmcm9tIG5ldXJhbCBuZXR3b3JrIG1vZGVsIHdpdGhvdXQgUENBLiBIb3dldmVyLCB0aGlzIG1vZGVsIHdpdGggUENBIGNvbnRhaW5zIGxvd2VyIG51bWJlciBvZiBmYWxzZSBwb3NpdGl2ZXMgYW5kIGFzIHN1Y2ggbmV1cmFsIG5ldHdvcmsgY2xhc3NpZmljYXRpb24gd2l0aCBQQ0EgaXMgcmVjb21tZW5kZWQgb3ZlciB0aGUgbmV1cmFsIG5ldHdvcmsgd2l0aG91dCBQQ0EuCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPUZBTFNFfQpzZXQuc2VlZCgzMCkKTk5Nb2RlbDIgPC0gdHJhaW4odHJhaW5pbmdEYXRhWywtMjBdLCB0cmFpbmluZ0RhdGEkeSwKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJubmV0IiwKICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJwY2EiKSwKICAgICAgICAgICAgICAgIHRyQ29udHJvbD0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKSwKICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShzaXplID0gNSwKICAgICAgICAgICAgICAgIGRlY2F5ID0gMCkpCmBgYApgYGB7cn0KTk5wcmVkaWN0aW9uczIgPC1wcmVkaWN0KE5OTW9kZWwyLCB0ZXN0RGF0YSwgbmEuYWN0aW9uID0gbmEucGFzcykKY29uZnVzaW9uTWF0cml4KE5OcHJlZGljdGlvbnMyLCB0ZXN0RGF0YSR5KQpgYGAKCg==