Loading Libraries

library(tidyverse)
library(caret)
library(randomForest)
library(skimr)
library(ggplot2)
library(gridExtra )
library(caTools)
library(corrplot)
library(ggcorrplot)
library(kableExtra)
library(caret)
library(tree)
library(stringr)

Reading the Data

cen<-read.csv("./census.csv")

1. Data Preprocessing

Exploring the data

str(cen)
'data.frame':   32561 obs. of  15 variables:
 $ age           : int  39 50 38 53 28 37 49 52 31 42 ...
 $ workclass     : chr  " State-gov" " Self-emp-not-inc" " Private" " Private" ...
 $ fnlwgt        : int  77516 83311 215646 234721 338409 284582 160187 209642 45781 159449 ...
 $ education     : chr  " Bachelors" " Bachelors" " HS-grad" " 11th" ...
 $ education.num : int  13 13 9 7 13 14 5 9 14 13 ...
 $ marital.status: chr  " Never-married" " Married-civ-spouse" " Divorced" " Married-civ-spouse" ...
 $ occupation    : chr  " Adm-clerical" " Exec-managerial" " Handlers-cleaners" " Handlers-cleaners" ...
 $ relationship  : chr  " Not-in-family" " Husband" " Not-in-family" " Husband" ...
 $ race          : chr  " White" " White" " White" " Black" ...
 $ sex           : chr  " Male" " Male" " Male" " Male" ...
 $ capital.gain  : int  2174 0 0 0 0 0 0 0 14084 5178 ...
 $ capital.loss  : int  0 0 0 0 0 0 0 0 0 0 ...
 $ hours.per.week: int  40 13 40 40 40 40 16 45 50 40 ...
 $ native.country: chr  " United-States" " United-States" " United-States" " United-States" ...
 $ X             : chr  " <=50K" " <=50K" " <=50K" " <=50K" ...
summary(cen)
      age         workclass             fnlwgt         education         education.num  
 Min.   :17.00   Length:32561       Min.   :  12285   Length:32561       Min.   : 1.00  
 1st Qu.:28.00   Class :character   1st Qu.: 117827   Class :character   1st Qu.: 9.00  
 Median :37.00   Mode  :character   Median : 178356   Mode  :character   Median :10.00  
 Mean   :38.58                      Mean   : 189778                      Mean   :10.08  
 3rd Qu.:48.00                      3rd Qu.: 237051                      3rd Qu.:12.00  
 Max.   :90.00                      Max.   :1484705                      Max.   :16.00  
 marital.status      occupation        relationship           race               sex           
 Length:32561       Length:32561       Length:32561       Length:32561       Length:32561      
 Class :character   Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                               
                                                                                               
                                                                                               
  capital.gain    capital.loss    hours.per.week  native.country          X            
 Min.   :    0   Min.   :   0.0   Min.   : 1.00   Length:32561       Length:32561      
 1st Qu.:    0   1st Qu.:   0.0   1st Qu.:40.00   Class :character   Class :character  
 Median :    0   Median :   0.0   Median :40.00   Mode  :character   Mode  :character  
 Mean   : 1078   Mean   :  87.3   Mean   :40.44                                        
 3rd Qu.:    0   3rd Qu.:   0.0   3rd Qu.:45.00                                        
 Max.   :99999   Max.   :4356.0   Max.   :99.00                                        

Counting NAs

colSums(is.na(cen))
           age      workclass         fnlwgt      education  education.num marital.status 
             0              0              0              0              0              0 
    occupation   relationship           race            sex   capital.gain   capital.loss 
             0              0              0              0              0              0 
hours.per.week native.country              X 
             0              0              0 

Insights

- There is a “?” in workclass, occupation and native country

- No NA values at this point

Trimming all the white spaces

cen %>% 
  mutate(across(where(is.character), str_trim))->cen

Replacing blank/missing values with NA

cen[cen==" "]<-NA

Let us count NAs once again

colSums(is.na(cen))
           age      workclass         fnlwgt      education  education.num marital.status 
             0              0              0              0              0              0 
    occupation   relationship           race            sex   capital.gain   capital.loss 
             0              0              0              0              0              0 
hours.per.week native.country              X 
             0              0              0 

Since we have few “?” in the data. Let us replace that with NA

cen[cen=='?']<-NA

Let us count NAs again

colSums(is.na(cen))
           age      workclass         fnlwgt      education  education.num marital.status 
             0           1836              0              0              0              0 
    occupation   relationship           race            sex   capital.gain   capital.loss 
          1843              0              0              0              0              0 
hours.per.week native.country              X 
             0            583              0 

Now we have NAs in occupation, workclass and native.country

Let us drop the rows with NAs

cen<-drop_na(cen)

Let us count NAs again

colSums(is.na(cen))
           age      workclass         fnlwgt      education  education.num marital.status 
             0              0              0              0              0              0 
    occupation   relationship           race            sex   capital.gain   capital.loss 
             0              0              0              0              0              0 
hours.per.week native.country              X 
             0              0              0 

All rows with NAs are now removed

2. Data Manipulation

a. Extracting education column

census_ed<-cen %>% 
  select(education)

b. Extracting columns from age to relationship

census_seq<-cen %>% 
  select(1:8)

c. Extracting columns 5,8, and 11

census_col<-cen %>% 
  select(5,8,11)

d. Extracting male employees who work in state_gov.

male_gov<- cen %>% 
  filter( sex=="Male", workclass=="State-gov")

e. Extracting all 39 year olds who either have a bachelor degree or is a US citizen

census_us<- cen %>% 
  filter( age == 39 ) %>% 
  filter(education=="Bachelors" | native.country=="United-States")

f. Extracting 200 random rows

set.seed(123)
census_200<-cen %>% 
  sample_n(200)

g. Count of different levels in workclass column

table(cen$workclass)

     Federal-gov        Local-gov          Private     Self-emp-inc Self-emp-not-inc 
             943             2067            22286             1074             2499 
       State-gov      Without-pay 
            1279               14 

h. Calculating the mean of capital.gain column grouped by workclass

cen %>% 
  group_by(workclass) %>% 
  summarise(Mean=mean(capital.gain)) %>% 
  kable()
workclass Mean
Federal-gov 832.3213
Local-gov 829.2303
Private 879.8582
Self-emp-inc 4810.7467
Self-emp-not-inc 1913.1345
State-gov 684.3065
Without-pay 487.8571

i. Create a separate dataframe with the details of males and females from the census data that has income more than 50,000.

census_male_50k<-cen %>% 
  filter(sex == "Male", X==">50K")
census_female_50K<-cen %>% 
  filter(sex == "Female", X==">50K")

j. Calculate the percentage of people from the United States who are private employees and earn less than 50,000 annually.

Number<-cen %>% 
  filter(native.country == "United-States", workclass == "Private", X =="<=50K") %>% 
  summarise(Count = n())

Number_us<-cen %>% 
  filter(native.country== "United-States") %>% 
  summarise(Count_us=n())

Result<-(Number/Number_us)*100

as.character(Result)
[1] "56.6972076788831"

k. Calculate the percentage of married people in the census data

total<-cen %>% 
  nrow()

married<-cen %>% 
  filter(marital.status=="Married-AF-spouse"|marital.status==
           "Married-civ-spouse"|marital.status =="Married-spouse-absent") %>% 
  nrow()
  

Result1<-(married/total)*100
Result1
[1] 47.92786

l. Calculate the percentage of high school graduates earning more than

50,000 annually.

hs<-cen %>% 
  filter(education=="HS-grad") %>% 
  nrow()


grtr<-cen %>% 
  filter(education=="HS-grad" & X==">50K") %>% 
  nrow()

Result2<-(grtr/hs)*100
Result2
[1] 16.43293

3. Data Visualization

Relationship vs Race vs Sex

bar1<- cen %>% 
  ggplot(aes(x=relationship, fill=race))+
  geom_bar(position = "dodge") + xlab("Categories of Relationship")+ylab("Count of Categories")+
  ggtitle("Distribution of Relationships by Race")+
  scale_x_discrete(labels = function(x) str_wrap(x, width = 10))

bar2<- cen %>% 
  ggplot(aes(x=relationship, fill=sex))+
  geom_bar(position = "dodge") + xlab("Categories of Relationship")+ylab("Count of Categories")+
  ggtitle("Distribution of Relationships by Sex")+
  scale_x_discrete(labels = function(x) str_wrap(x, width = 10))

grid.arrange(bar1,bar2, ncol=1)

Distribution of age

cen %>% 
  ggplot(aes(x=age, fill=X))+geom_histogram(bins=50)+
  guides(fill=guide_legend("Yearly Income"))+
  ggtitle("Distribution of Age")+
  theme_bw()

Scatter Plot

cen %>% 
  ggplot(aes(x=capital.gain, y=hours.per.week, color=X))+
  geom_point(size= 2, alpha=.40)+
  ggtitle("Capital Gain vs Hours per week by income")+
  xlab("Capital Gain")+ylab("Hours per week")+
  guides(color=guide_legend("Yearly Income"))

Box plot

cen %>% 
  ggplot(aes(x=education, y=age, fill=sex))+ geom_boxplot()+
  scale_x_discrete(labels = function(x) str_wrap(x, width = 10))+
  ggtitle("Box plot of Age by Education and Sex")

4. Logistic Regression

a) Build a simple logistic regression model as follows:

Divide the dataset into training and test sets in 65:35 ratio.

sample.split(cen$X,SplitRatio = .65)->split_tag1
train1<-subset(cen,split_tag1==T)
test1<-subset(cen,split_tag1==F)

Build a logistic regression model where the dependent variable is “X”(yearly income) and the independent variable is “occupation”.

train1$X<-as.factor(train1$X)
train1$occupation<-as.factor(train1$occupation)
test1$X<-as.factor(test1$X)
test1$occupation<-as.factor(test1$occupation)

slm<-glm(X~occupation,data = train1, family = "binomial")
summary(slm)

Call:
glm(formula = X ~ occupation, family = "binomial", data = train1)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.1661  -0.7923  -0.5266  -0.1404   3.0414  

Coefficients:
                             Estimate Std. Error z value Pr(>|z|)    
(Intercept)                  -1.90552    0.06048 -31.504  < 2e-16 ***
occupationArmed-Forces      -10.66054  122.74159  -0.087  0.93079    
occupationCraft-repair        0.66926    0.07658   8.739  < 2e-16 ***
occupationExec-managerial     1.87875    0.07203  26.083  < 2e-16 ***
occupationFarming-fishing    -0.06819    0.13654  -0.499  0.61749    
occupationHandlers-cleaners  -0.90199    0.15636  -5.769 8.00e-09 ***
occupationMachine-op-inspct  -0.02626    0.10336  -0.254  0.79941    
occupationOther-service      -1.21740    0.12513  -9.729  < 2e-16 ***
occupationPriv-house-serv    -2.70960    1.00676  -2.691  0.00712 ** 
occupationProf-specialty      1.65054    0.07212  22.885  < 2e-16 ***
occupationProtective-serv     1.20172    0.11973  10.037  < 2e-16 ***
occupationSales               0.90792    0.07641  11.883  < 2e-16 ***
occupationTech-support        1.11657    0.10753  10.384  < 2e-16 ***
occupationTransport-moving    0.57402    0.09823   5.844 5.11e-09 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 22002  on 19604  degrees of freedom
Residual deviance: 19459  on 19591  degrees of freedom
AIC: 19487

Number of Fisher Scoring iterations: 11

Predict the values on the test set.

pred<-predict(slm, newdata = test1, type = 'response')

Build a confusion matrix and find the accuracy

#Let us consider a threshold of 30%
table(test1$X,pred>.3)
       
        FALSE TRUE
  <=50K  6076 1853
  >50K   1156 1472
acc1<-(6076+1472)/(6076+1472+1156+1853)
acc1
[1] 0.7149758

b)Build a multiple logistic regression model as follows:

Divide the dataset into training and test sets in 80:20 ratio.

sample.split(cen$X,SplitRatio = .8)->split_tag2
train2<-subset(cen,split_tag2==T)
test2<-subset(cen,split_tag2==F)

Build a logistic regression model where the dependent variable is “X”(yearly income) and independent variables are “age”, “workclass”, and“education”.

train2$X<-as.factor(train2$X)
train2$occupation<-as.factor(train2$occupation)
test2$X<-as.factor(test2$X)
test2$occupation<-as.factor(test2$occupation)

mlm<-glm(X~age+workclass+education,data = train2, family = "binomial")
summary(mlm)

Call:
glm(formula = X ~ age + workclass + education, family = "binomial", 
    data = train2)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-2.45453  -0.71218  -0.47802  -0.00173   2.89906  

Coefficients:
                            Estimate Std. Error z value Pr(>|z|)    
(Intercept)                -4.298522   0.189794 -22.648  < 2e-16 ***
age                         0.044976   0.001361  33.042  < 2e-16 ***
workclassLocal-gov         -0.505908   0.101988  -4.960 7.03e-07 ***
workclassPrivate           -0.284586   0.084748  -3.358 0.000785 ***
workclassSelf-emp-inc       0.700984   0.113648   6.168 6.91e-10 ***
workclassSelf-emp-not-inc  -0.434137   0.099636  -4.357 1.32e-05 ***
workclassState-gov         -0.709368   0.114761  -6.181 6.36e-10 ***
workclassWithout-pay      -13.026821 169.196138  -0.077 0.938630    
education11th               0.139019   0.216794   0.641 0.521360    
education12th               0.285689   0.283850   1.006 0.314186    
education1st-4th           -0.787269   0.487168  -1.616 0.106092    
education5th-6th           -0.372229   0.338497  -1.100 0.271482    
education7th-8th           -0.426406   0.252444  -1.689 0.091199 .  
education9th               -0.326394   0.280973  -1.162 0.245375    
educationAssoc-acdm         1.794219   0.176564  10.162  < 2e-16 ***
educationAssoc-voc          1.766029   0.172192  10.256  < 2e-16 ***
educationBachelors          2.509785   0.159602  15.725  < 2e-16 ***
educationDoctorate          3.699814   0.210276  17.595  < 2e-16 ***
educationHS-grad            1.091805   0.158773   6.877 6.13e-12 ***
educationMasters            2.939926   0.166614  17.645  < 2e-16 ***
educationPreschool        -10.995295  88.674904  -0.124 0.901319    
educationProf-school        3.736719   0.194735  19.189  < 2e-16 ***
educationSome-college       1.468588   0.159919   9.183  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 27079  on 24128  degrees of freedom
Residual deviance: 22458  on 24106  degrees of freedom
AIC: 22504

Number of Fisher Scoring iterations: 12

Predict the values on the test set.

pred1<-predict(mlm, newdata = test2, type = "response")

Build a confusion matrix and find the accuracy.

#Lets us consider a threshold of 30%
table(test2$X,pred1>.3)
       
        FALSE TRUE
  <=50K  3586  945
  >50K    616  886
acc2<-(3527+891)/(3527+891+1004+611)
acc2
[1] 0.7323057

5. Decision Tree

a) Build a decision tree model as follows:

Divide the dataset into training and test sets in 70:30 ratio.

sample.split(cen$X, SplitRatio = .7)->split_tag3
train3<-subset(cen,split_tag3==T)
test3<-subset(cen, split_tag3==F)

Build a decision tree model where the dependent variable is “X”(Yearly Income) and the rest of the variables as independent variables.

# We need to first convert the independent variables to factors

train3$workclass<-as.factor(train3$workclass)
train3$sex<-as.factor(train3$sex)
train3$occupation<-as.factor(train3$occupation)
train3$education<-as.factor(train3$education)
train3$X<-as.factor(train3$X)
train3$marital.status<-as.factor(train3$marital.status)

# Building model

treemod<-tree(X~sex+occupation+education+marital.status+workclass, data = train3)
plot(treemod)
text(treemod)

Predict the values on the test set.

# We need to first convert the independent variables to factors

test3$workclass<-as.factor(test3$workclass)
test3$sex<-as.factor(test3$sex)
test3$occupation<-as.factor(test3$occupation)
test3$education<-as.factor(test3$education)
test3$X<-as.factor(test3$X)
test3$marital.status<-as.factor(test3$marital.status)

# Predicting 
pred3<-predict(treemod, newdata = test3, type = "class")

Build a confusion matrix and calculate the accuracy

table(test3$X,pred3)
       pred3
        <=50K >50K
  <=50K  6470  326
  >50K   1343  909
acc3<-(6470+926)/(6470+926+1326+926)
acc3
[1] 0.7665837

6. Random Forest:

a) Build a random forest model as follows:

Divide the dataset into training and test sets in 80:20 ratio.

sample.split(cen$X, SplitRatio = .8)->split_tag4
train4<-subset(cen,split_tag4==T)
test4<-subset(cen, split_tag4==F)

Build a random forest model where the dependent variable is “X”(Yearly Income)and the rest of the variables as independent variables and number of trees as 300.

# We need to first convert the independent variables to factors

train4$workclass<-as.factor(train4$workclass)
train4$sex<-as.factor(train4$sex)
train4$occupation<-as.factor(train4$occupation)
train4$education<-as.factor(train4$education)
train4$X<-as.factor(train4$X)
train4$marital.status<-as.factor(train4$marital.status)

#Building Model
randomForest(X~workclass+sex+occupation+education+marital.status, 
             data=train4, mtry =3, ntree =500)->RFmod
# Finding the importance of independent variables
importance(RFmod)
               MeanDecreaseGini
workclass              297.0319
sex                    132.4960
occupation             730.2710
education             1098.5017
marital.status        1780.9738
#Plotting
varImpPlot(RFmod)

Predict values on the test set


# We need to first convert the independent variables to factors

test4$workclass<-as.factor(test4$workclass)
test4$sex<-as.factor(test4$sex)
test4$occupation<-as.factor(test4$occupation)
test4$education<-as.factor(test4$education)
test4$X<-as.factor(test4$X)
test4$marital.status<-as.factor(test4$marital.status)

#Predicting

levels(test4$workclass) <- levels(train4$workclass)
pred4<-predict(RFmod,newdata = test4, type = "class")

Build a confusion matrix and calculate the accuracy

table(test4$X,pred4)
       pred4
        <=50K >50K
  <=50K  4136  395
  >50K    679  823
acc4<-(4136+823)/(4136+823+679+395)
acc4
[1] 0.8219791

LS0tDQp0aXRsZTogIkNlbnN1cyBJbmNvbWUgUHJvamVjdCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KIyBMb2FkaW5nIExpYnJhcmllcw0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoc2tpbXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdyaWRFeHRyYSApDQpsaWJyYXJ5KGNhVG9vbHMpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShnZ2NvcnJwbG90KQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkodHJlZSkNCmxpYnJhcnkoc3RyaW5ncikNCmBgYA0KDQojIFJlYWRpbmcgdGhlIERhdGENCmBgYHtyfQ0KY2VuPC1yZWFkLmNzdigiLi9jZW5zdXMuY3N2IikNCmBgYA0KIyAxLiBEYXRhIFByZXByb2Nlc3NpbmcNCg0KIyMgRXhwbG9yaW5nIHRoZSBkYXRhDQpgYGB7cn0NCnN0cihjZW4pDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGNlbikNCmBgYA0KIyMgQ291bnRpbmcgTkFzDQpgYGB7cn0NCmNvbFN1bXMoaXMubmEoY2VuKSkNCmBgYA0KIyBJbnNpZ2h0cw0KIyMjIC0gVGhlcmUgaXMgYSAiPyIgaW4gd29ya2NsYXNzLCBvY2N1cGF0aW9uIGFuZCBuYXRpdmUgY291bnRyeQ0KIyMjIC0gTm8gTkEgdmFsdWVzIGF0IHRoaXMgcG9pbnQNCg0KIyMgVHJpbW1pbmcgYWxsIHRoZSB3aGl0ZSBzcGFjZXMNCmBgYHtyfQ0KY2VuICU+JSANCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCBzdHJfdHJpbSkpLT5jZW4NCmBgYA0KIyMgUmVwbGFjaW5nIGJsYW5rL21pc3NpbmcgdmFsdWVzIHdpdGggTkENCmBgYHtyfQ0KY2VuW2Nlbj09IiAiXTwtTkENCmBgYA0KIyMgTGV0IHVzIGNvdW50IE5BcyBvbmNlIGFnYWluDQpgYGB7cn0NCmNvbFN1bXMoaXMubmEoY2VuKSkNCmBgYA0KIyMgU2luY2Ugd2UgaGF2ZSBmZXcgIj8iIGluIHRoZSBkYXRhLiBMZXQgdXMgcmVwbGFjZSB0aGF0IHdpdGggTkENCmBgYHtyfQ0KY2VuW2Nlbj09Jz8nXTwtTkENCmBgYA0KIyMgTGV0IHVzIGNvdW50IE5BcyBhZ2Fpbg0KYGBge3J9DQpjb2xTdW1zKGlzLm5hKGNlbikpDQpgYGANCiMjIyBOb3cgd2UgaGF2ZSBOQXMgaW4gb2NjdXBhdGlvbiwgd29ya2NsYXNzIGFuZCBuYXRpdmUuY291bnRyeQ0KDQojIyBMZXQgdXMgZHJvcCB0aGUgcm93cyB3aXRoIE5Bcw0KYGBge3J9DQpjZW48LWRyb3BfbmEoY2VuKQ0KYGBgDQojIyBMZXQgdXMgY291bnQgTkFzIGFnYWluDQpgYGB7cn0NCmNvbFN1bXMoaXMubmEoY2VuKSkNCmBgYA0KIyMgQWxsIHJvd3Mgd2l0aCBOQXMgYXJlIG5vdyByZW1vdmVkDQoNCiMgMi4gRGF0YSBNYW5pcHVsYXRpb24NCiMjIGEuIEV4dHJhY3RpbmcgZWR1Y2F0aW9uIGNvbHVtbg0KYGBge3J9DQpjZW5zdXNfZWQ8LWNlbiAlPiUgDQogIHNlbGVjdChlZHVjYXRpb24pDQpgYGANCiMjIGIuIEV4dHJhY3RpbmcgY29sdW1ucyBmcm9tIGFnZSB0byByZWxhdGlvbnNoaXANCmBgYHtyfQ0KY2Vuc3VzX3NlcTwtY2VuICU+JSANCiAgc2VsZWN0KDE6OCkNCmBgYA0KIyMgYy4gRXh0cmFjdGluZyBjb2x1bW5zIDUsOCwgYW5kIDExDQpgYGB7cn0NCmNlbnN1c19jb2w8LWNlbiAlPiUgDQogIHNlbGVjdCg1LDgsMTEpDQpgYGANCiMjIGQuIEV4dHJhY3RpbmcgbWFsZSBlbXBsb3llZXMgd2hvIHdvcmsgaW4gc3RhdGVfZ292Lg0KYGBge3J9DQptYWxlX2dvdjwtIGNlbiAlPiUgDQogIGZpbHRlciggc2V4PT0iTWFsZSIsIHdvcmtjbGFzcz09IlN0YXRlLWdvdiIpDQpgYGANCiMjIGUuIEV4dHJhY3RpbmcgYWxsIDM5IHllYXIgb2xkcyB3aG8gZWl0aGVyIGhhdmUgYSBiYWNoZWxvciBkZWdyZWUgb3IgaXMgYSBVUyBjaXRpemVuDQpgYGB7cn0NCmNlbnN1c191czwtIGNlbiAlPiUgDQogIGZpbHRlciggYWdlID09IDM5ICkgJT4lIA0KICBmaWx0ZXIoZWR1Y2F0aW9uPT0iQmFjaGVsb3JzIiB8IG5hdGl2ZS5jb3VudHJ5PT0iVW5pdGVkLVN0YXRlcyIpDQoNCmBgYA0KDQojIyBmLiBFeHRyYWN0aW5nIDIwMCByYW5kb20gcm93cw0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpjZW5zdXNfMjAwPC1jZW4gJT4lIA0KICBzYW1wbGVfbigyMDApDQpgYGANCg0KIyMgZy4gQ291bnQgb2YgZGlmZmVyZW50IGxldmVscyBpbiB3b3JrY2xhc3MgY29sdW1uDQpgYGB7cn0NCnRhYmxlKGNlbiR3b3JrY2xhc3MpDQpgYGANCg0KDQojIyBoLiBDYWxjdWxhdGluZyB0aGUgbWVhbiBvZiBjYXBpdGFsLmdhaW4gY29sdW1uIGdyb3VwZWQgYnkgd29ya2NsYXNzDQoNCmBgYHtyfQ0KY2VuICU+JSANCiAgZ3JvdXBfYnkod29ya2NsYXNzKSAlPiUgDQogIHN1bW1hcmlzZShNZWFuPW1lYW4oY2FwaXRhbC5nYWluKSkgJT4lIA0KICBrYWJsZSgpDQpgYGANCiMjIGkuIENyZWF0ZSBhIHNlcGFyYXRlIGRhdGFmcmFtZSB3aXRoIHRoZSBkZXRhaWxzIG9mIG1hbGVzIGFuZCBmZW1hbGVzIGZyb20gdGhlIGNlbnN1cyBkYXRhIHRoYXQgaGFzIGluY29tZSBtb3JlIHRoYW4gNTAsMDAwLg0KYGBge3J9DQpjZW5zdXNfbWFsZV81MGs8LWNlbiAlPiUgDQogIGZpbHRlcihzZXggPT0gIk1hbGUiLCBYPT0iPjUwSyIpDQpjZW5zdXNfZmVtYWxlXzUwSzwtY2VuICU+JSANCiAgZmlsdGVyKHNleCA9PSAiRmVtYWxlIiwgWD09Ij41MEsiKQ0KYGBgDQoNCiMjICBqLiBDYWxjdWxhdGUgdGhlIHBlcmNlbnRhZ2Ugb2YgcGVvcGxlIGZyb20gdGhlIFVuaXRlZCBTdGF0ZXMgd2hvIGFyZSBwcml2YXRlIGVtcGxveWVlcyBhbmQgZWFybiBsZXNzIHRoYW4gNTAsMDAwIGFubnVhbGx5Lg0KYGBge3J9DQpOdW1iZXI8LWNlbiAlPiUgDQogIGZpbHRlcihuYXRpdmUuY291bnRyeSA9PSAiVW5pdGVkLVN0YXRlcyIsIHdvcmtjbGFzcyA9PSAiUHJpdmF0ZSIsIFggPT0iPD01MEsiKSAlPiUgDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkNCg0KTnVtYmVyX3VzPC1jZW4gJT4lIA0KICBmaWx0ZXIobmF0aXZlLmNvdW50cnk9PSAiVW5pdGVkLVN0YXRlcyIpICU+JSANCiAgc3VtbWFyaXNlKENvdW50X3VzPW4oKSkNCg0KUmVzdWx0PC0oTnVtYmVyL051bWJlcl91cykqMTAwDQoNCmFzLmNoYXJhY3RlcihSZXN1bHQpDQoNCmBgYA0KIyMgay4gQ2FsY3VsYXRlIHRoZSBwZXJjZW50YWdlIG9mIG1hcnJpZWQgcGVvcGxlIGluIHRoZSBjZW5zdXMgZGF0YQ0KYGBge3J9DQp0b3RhbDwtY2VuICU+JSANCiAgbnJvdygpDQoNCm1hcnJpZWQ8LWNlbiAlPiUgDQogIGZpbHRlcihtYXJpdGFsLnN0YXR1cz09Ik1hcnJpZWQtQUYtc3BvdXNlInxtYXJpdGFsLnN0YXR1cz09DQogICAgICAgICAgICJNYXJyaWVkLWNpdi1zcG91c2UifG1hcml0YWwuc3RhdHVzID09Ik1hcnJpZWQtc3BvdXNlLWFic2VudCIpICU+JSANCiAgbnJvdygpDQogIA0KDQpSZXN1bHQxPC0obWFycmllZC90b3RhbCkqMTAwDQpSZXN1bHQxDQpgYGANCiMjIGwuIENhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiBoaWdoIHNjaG9vbCBncmFkdWF0ZXMgZWFybmluZyBtb3JlIHRoYW4NCiMjIDUwLDAwMCBhbm51YWxseS4NCg0KYGBge3J9DQpoczwtY2VuICU+JSANCiAgZmlsdGVyKGVkdWNhdGlvbj09IkhTLWdyYWQiKSAlPiUgDQogIG5yb3coKQ0KDQoNCmdydHI8LWNlbiAlPiUgDQogIGZpbHRlcihlZHVjYXRpb249PSJIUy1ncmFkIiAmIFg9PSI+NTBLIikgJT4lIA0KICBucm93KCkNCg0KUmVzdWx0MjwtKGdydHIvaHMpKjEwMA0KUmVzdWx0Mg0KYGBgDQoNCiMgMy4gRGF0YSBWaXN1YWxpemF0aW9uDQoNCiMjIFJlbGF0aW9uc2hpcCB2cyBSYWNlIHZzIFNleA0KYGBge3J9DQpiYXIxPC0gY2VuICU+JSANCiAgZ2dwbG90KGFlcyh4PXJlbGF0aW9uc2hpcCwgZmlsbD1yYWNlKSkrDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgKyB4bGFiKCJDYXRlZ29yaWVzIG9mIFJlbGF0aW9uc2hpcCIpK3lsYWIoIkNvdW50IG9mIENhdGVnb3JpZXMiKSsNCiAgZ2d0aXRsZSgiRGlzdHJpYnV0aW9uIG9mIFJlbGF0aW9uc2hpcHMgYnkgUmFjZSIpKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTApKQ0KDQpiYXIyPC0gY2VuICU+JSANCiAgZ2dwbG90KGFlcyh4PXJlbGF0aW9uc2hpcCwgZmlsbD1zZXgpKSsNCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArIHhsYWIoIkNhdGVnb3JpZXMgb2YgUmVsYXRpb25zaGlwIikreWxhYigiQ291bnQgb2YgQ2F0ZWdvcmllcyIpKw0KICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgUmVsYXRpb25zaGlwcyBieSBTZXgiKSsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBmdW5jdGlvbih4KSBzdHJfd3JhcCh4LCB3aWR0aCA9IDEwKSkNCg0KZ3JpZC5hcnJhbmdlKGJhcjEsYmFyMiwgbmNvbD0xKQ0KYGBgDQoNCiMjIERpc3RyaWJ1dGlvbiBvZiBhZ2UNCg0KYGBge3J9DQpjZW4gJT4lIA0KICBnZ3Bsb3QoYWVzKHg9YWdlLCBmaWxsPVgpKStnZW9tX2hpc3RvZ3JhbShiaW5zPTUwKSsNCiAgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKCJZZWFybHkgSW5jb21lIikpKw0KICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgQWdlIikrDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyBTY2F0dGVyIFBsb3QNCg0KYGBge3J9DQpjZW4gJT4lIA0KICBnZ3Bsb3QoYWVzKHg9Y2FwaXRhbC5nYWluLCB5PWhvdXJzLnBlci53ZWVrLCBjb2xvcj1YKSkrDQogIGdlb21fcG9pbnQoc2l6ZT0gMiwgYWxwaGE9LjQwKSsNCiAgZ2d0aXRsZSgiQ2FwaXRhbCBHYWluIHZzIEhvdXJzIHBlciB3ZWVrIGJ5IGluY29tZSIpKw0KICB4bGFiKCJDYXBpdGFsIEdhaW4iKSt5bGFiKCJIb3VycyBwZXIgd2VlayIpKw0KICBndWlkZXMoY29sb3I9Z3VpZGVfbGVnZW5kKCJZZWFybHkgSW5jb21lIikpDQpgYGANCg0KIyMgQm94IHBsb3QNCmBgYHtyfQ0KY2VuICU+JSANCiAgZ2dwbG90KGFlcyh4PWVkdWNhdGlvbiwgeT1hZ2UsIGZpbGw9c2V4KSkrIGdlb21fYm94cGxvdCgpKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGZ1bmN0aW9uKHgpIHN0cl93cmFwKHgsIHdpZHRoID0gMTApKSsNCiAgZ2d0aXRsZSgiQm94IHBsb3Qgb2YgQWdlIGJ5IEVkdWNhdGlvbiBhbmQgU2V4IikNCg0KYGBgDQoNCg0KIyA0LiBMb2dpc3RpYyBSZWdyZXNzaW9uDQoNCiMjIGEpIEJ1aWxkIGEgc2ltcGxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgYXMgZm9sbG93czoNCg0KIyMjIERpdmlkZSB0aGUgZGF0YXNldCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMgaW4gNjU6MzUgcmF0aW8uDQoNCmBgYHtyfQ0Kc2FtcGxlLnNwbGl0KGNlbiRYLFNwbGl0UmF0aW8gPSAuNjUpLT5zcGxpdF90YWcxDQp0cmFpbjE8LXN1YnNldChjZW4sc3BsaXRfdGFnMT09VCkNCnRlc3QxPC1zdWJzZXQoY2VuLHNwbGl0X3RhZzE9PUYpDQpgYGANCg0KIyMjIEJ1aWxkIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3aGVyZSB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIGlzIOKAnFjigJ0oeWVhcmx5IGluY29tZSkgYW5kIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZSBpcyDigJxvY2N1cGF0aW9u4oCdLiANCg0KYGBge3J9DQp0cmFpbjEkWDwtYXMuZmFjdG9yKHRyYWluMSRYKQ0KdHJhaW4xJG9jY3VwYXRpb248LWFzLmZhY3Rvcih0cmFpbjEkb2NjdXBhdGlvbikNCnRlc3QxJFg8LWFzLmZhY3Rvcih0ZXN0MSRYKQ0KdGVzdDEkb2NjdXBhdGlvbjwtYXMuZmFjdG9yKHRlc3QxJG9jY3VwYXRpb24pDQoNCnNsbTwtZ2xtKFh+b2NjdXBhdGlvbixkYXRhID0gdHJhaW4xLCBmYW1pbHkgPSAiYmlub21pYWwiKQ0Kc3VtbWFyeShzbG0pDQpgYGANCg0KIyMjIFByZWRpY3QgdGhlIHZhbHVlcyBvbiB0aGUgdGVzdCBzZXQuIA0KDQpgYGB7cn0NCnByZWQ8LXByZWRpY3Qoc2xtLCBuZXdkYXRhID0gdGVzdDEsIHR5cGUgPSAncmVzcG9uc2UnKQ0KYGBgDQoNCiMjIyBCdWlsZCBhIGNvbmZ1c2lvbiBtYXRyaXggYW5kIGZpbmQgdGhlIGFjY3VyYWN5DQoNCmBgYHtyfQ0KI0xldCB1cyBjb25zaWRlciBhIHRocmVzaG9sZCBvZiAzMCUNCnRhYmxlKHRlc3QxJFgscHJlZD4uMykNCmBgYA0KYGBge3J9DQphY2MxPC0oNjA3NisxNDcyKS8oNjA3NisxNDcyKzExNTYrMTg1MykNCmFjYzENCmBgYA0KXHRleHRjb2xvcntibHVlfXtJbiB0aGlzIGNhc2UsIHdlIGFyZSBnZXR0aW5nIGFuIGFjY3VyYWN5IG9mIDcxIHBlcmNlbnRhZ2UufQ0KDQoNCiMjIGIpQnVpbGQgYSBtdWx0aXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGFzIGZvbGxvd3M6DQojIyMgRGl2aWRlIHRoZSBkYXRhc2V0IGludG8gdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cyBpbiA4MDoyMCByYXRpby4gDQpgYGB7cn0NCnNhbXBsZS5zcGxpdChjZW4kWCxTcGxpdFJhdGlvID0gLjgpLT5zcGxpdF90YWcyDQp0cmFpbjI8LXN1YnNldChjZW4sc3BsaXRfdGFnMj09VCkNCnRlc3QyPC1zdWJzZXQoY2VuLHNwbGl0X3RhZzI9PUYpDQpgYGANCg0KIyMjIEJ1aWxkIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3aGVyZSB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIGlzIOKAnFjigJ0oeWVhcmx5IGluY29tZSkgYW5kIGluZGVwZW5kZW50IHZhcmlhYmxlcyBhcmUg4oCcYWdl4oCdLCDigJx3b3JrY2xhc3PigJ0sIGFuZOKAnGVkdWNhdGlvbuKAnS4gDQpgYGB7cn0NCnRyYWluMiRYPC1hcy5mYWN0b3IodHJhaW4yJFgpDQp0cmFpbjIkb2NjdXBhdGlvbjwtYXMuZmFjdG9yKHRyYWluMiRvY2N1cGF0aW9uKQ0KdGVzdDIkWDwtYXMuZmFjdG9yKHRlc3QyJFgpDQp0ZXN0MiRvY2N1cGF0aW9uPC1hcy5mYWN0b3IodGVzdDIkb2NjdXBhdGlvbikNCg0KbWxtPC1nbG0oWH5hZ2Urd29ya2NsYXNzK2VkdWNhdGlvbixkYXRhID0gdHJhaW4yLCBmYW1pbHkgPSAiYmlub21pYWwiKQ0Kc3VtbWFyeShtbG0pDQpgYGANCg0KIyMjIFByZWRpY3QgdGhlIHZhbHVlcyBvbiB0aGUgdGVzdCBzZXQuDQpgYGB7cn0NCnByZWQxPC1wcmVkaWN0KG1sbSwgbmV3ZGF0YSA9IHRlc3QyLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQojIyMgQnVpbGQgYSBjb25mdXNpb24gbWF0cml4IGFuZCBmaW5kIHRoZSBhY2N1cmFjeS4NCmBgYHtyfQ0KI0xldHMgdXMgY29uc2lkZXIgYSB0aHJlc2hvbGQgb2YgMzAlDQp0YWJsZSh0ZXN0MiRYLHByZWQxPi4zKQ0KYGBgDQpgYGB7cn0NCmFjYzI8LSgzNTI3Kzg5MSkvKDM1MjcrODkxKzEwMDQrNjExKQ0KYWNjMg0KYGBgDQoNClx0ZXh0Y29sb3J7Ymx1ZX17SW4gdGhpcyBjYXNlLCB3ZSBhcmUgZ2V0dGluZyBhbiBhY2N1cmFjeSBvZiA3MyBwZXJjZW50YWdlLn0NCg0KIyA1LiBEZWNpc2lvbiBUcmVlDQoNCiMjIGEpIEJ1aWxkIGEgZGVjaXNpb24gdHJlZSBtb2RlbCBhcyBmb2xsb3dzOg0KIyMjIERpdmlkZSB0aGUgZGF0YXNldCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMgaW4gNzA6MzAgcmF0aW8uIA0KYGBge3J9DQpzYW1wbGUuc3BsaXQoY2VuJFgsIFNwbGl0UmF0aW8gPSAuNyktPnNwbGl0X3RhZzMNCnRyYWluMzwtc3Vic2V0KGNlbixzcGxpdF90YWczPT1UKQ0KdGVzdDM8LXN1YnNldChjZW4sIHNwbGl0X3RhZzM9PUYpDQpgYGANCg0KIyMjIEJ1aWxkIGEgZGVjaXNpb24gdHJlZSBtb2RlbCB3aGVyZSB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIGlzIOKAnFjigJ0oWWVhcmx5IEluY29tZSkgYW5kIHRoZSByZXN0IG9mIHRoZSB2YXJpYWJsZXMgYXMgaW5kZXBlbmRlbnQgdmFyaWFibGVzLiANCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBXZSBuZWVkIHRvIGZpcnN0IGNvbnZlcnQgdGhlIGluZGVwZW5kZW50IHZhcmlhYmxlcyB0byBmYWN0b3JzDQoNCnRyYWluMyR3b3JrY2xhc3M8LWFzLmZhY3Rvcih0cmFpbjMkd29ya2NsYXNzKQ0KdHJhaW4zJHNleDwtYXMuZmFjdG9yKHRyYWluMyRzZXgpDQp0cmFpbjMkb2NjdXBhdGlvbjwtYXMuZmFjdG9yKHRyYWluMyRvY2N1cGF0aW9uKQ0KdHJhaW4zJGVkdWNhdGlvbjwtYXMuZmFjdG9yKHRyYWluMyRlZHVjYXRpb24pDQp0cmFpbjMkWDwtYXMuZmFjdG9yKHRyYWluMyRYKQ0KdHJhaW4zJG1hcml0YWwuc3RhdHVzPC1hcy5mYWN0b3IodHJhaW4zJG1hcml0YWwuc3RhdHVzKQ0KDQojIEJ1aWxkaW5nIG1vZGVsDQoNCnRyZWVtb2Q8LXRyZWUoWH5zZXgrb2NjdXBhdGlvbitlZHVjYXRpb24rbWFyaXRhbC5zdGF0dXMrd29ya2NsYXNzLCBkYXRhID0gdHJhaW4zKQ0KcGxvdCh0cmVlbW9kKQ0KdGV4dCh0cmVlbW9kKQ0KYGBgDQoNCiMjIyBQcmVkaWN0IHRoZSB2YWx1ZXMgb24gdGhlIHRlc3Qgc2V0LiANCmBgYHtyfQ0KIyBXZSBuZWVkIHRvIGZpcnN0IGNvbnZlcnQgdGhlIGluZGVwZW5kZW50IHZhcmlhYmxlcyB0byBmYWN0b3JzDQoNCnRlc3QzJHdvcmtjbGFzczwtYXMuZmFjdG9yKHRlc3QzJHdvcmtjbGFzcykNCnRlc3QzJHNleDwtYXMuZmFjdG9yKHRlc3QzJHNleCkNCnRlc3QzJG9jY3VwYXRpb248LWFzLmZhY3Rvcih0ZXN0MyRvY2N1cGF0aW9uKQ0KdGVzdDMkZWR1Y2F0aW9uPC1hcy5mYWN0b3IodGVzdDMkZWR1Y2F0aW9uKQ0KdGVzdDMkWDwtYXMuZmFjdG9yKHRlc3QzJFgpDQp0ZXN0MyRtYXJpdGFsLnN0YXR1czwtYXMuZmFjdG9yKHRlc3QzJG1hcml0YWwuc3RhdHVzKQ0KDQojIFByZWRpY3RpbmcgDQpwcmVkMzwtcHJlZGljdCh0cmVlbW9kLCBuZXdkYXRhID0gdGVzdDMsIHR5cGUgPSAiY2xhc3MiKQ0KYGBgDQoNCiMjIyBCdWlsZCBhIGNvbmZ1c2lvbiBtYXRyaXggYW5kIGNhbGN1bGF0ZSB0aGUgYWNjdXJhY3kNCmBgYHtyfQ0KdGFibGUodGVzdDMkWCxwcmVkMykNCmBgYA0KYGBge3J9DQphY2MzPC0oNjQ3MCs5MjYpLyg2NDcwKzkyNisxMzI2KzkyNikNCmFjYzMNCmBgYA0KXHRleHRjb2xvcntibHVlfXtJbiB0aGlzIGNhc2UsIHdlIGFyZSBnZXR0aW5nIGFuIGFjY3VyYWN5IG9mIDc2IHBlcmNlbnRhZ2UufQ0KDQojIDYuIFJhbmRvbSBGb3Jlc3Q6DQojIyBhKSBCdWlsZCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgYXMgZm9sbG93czoNCg0KIyMjIERpdmlkZSB0aGUgZGF0YXNldCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMgaW4gODA6MjAgcmF0aW8uDQpgYGB7cn0NCnNhbXBsZS5zcGxpdChjZW4kWCwgU3BsaXRSYXRpbyA9IC44KS0+c3BsaXRfdGFnNA0KdHJhaW40PC1zdWJzZXQoY2VuLHNwbGl0X3RhZzQ9PVQpDQp0ZXN0NDwtc3Vic2V0KGNlbiwgc3BsaXRfdGFnND09RikNCmBgYA0KDQojIyMgQnVpbGQgYSByYW5kb20gZm9yZXN0IG1vZGVsIHdoZXJlIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgaXMg4oCcWOKAnShZZWFybHkgSW5jb21lKWFuZCB0aGUgcmVzdCBvZiB0aGUgdmFyaWFibGVzIGFzIGluZGVwZW5kZW50IHZhcmlhYmxlcyBhbmQgbnVtYmVyIG9mIHRyZWVzIGFzIDMwMC4gDQpgYGB7cn0NCiMgV2UgbmVlZCB0byBmaXJzdCBjb252ZXJ0IHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgdG8gZmFjdG9ycw0KDQp0cmFpbjQkd29ya2NsYXNzPC1hcy5mYWN0b3IodHJhaW40JHdvcmtjbGFzcykNCnRyYWluNCRzZXg8LWFzLmZhY3Rvcih0cmFpbjQkc2V4KQ0KdHJhaW40JG9jY3VwYXRpb248LWFzLmZhY3Rvcih0cmFpbjQkb2NjdXBhdGlvbikNCnRyYWluNCRlZHVjYXRpb248LWFzLmZhY3Rvcih0cmFpbjQkZWR1Y2F0aW9uKQ0KdHJhaW40JFg8LWFzLmZhY3Rvcih0cmFpbjQkWCkNCnRyYWluNCRtYXJpdGFsLnN0YXR1czwtYXMuZmFjdG9yKHRyYWluNCRtYXJpdGFsLnN0YXR1cykNCg0KI0J1aWxkaW5nIE1vZGVsDQpyYW5kb21Gb3Jlc3QoWH53b3JrY2xhc3Mrc2V4K29jY3VwYXRpb24rZWR1Y2F0aW9uK21hcml0YWwuc3RhdHVzLCANCiAgICAgICAgICAgICBkYXRhPXRyYWluNCwgbXRyeSA9MywgbnRyZWUgPTUwMCktPlJGbW9kDQpgYGANCg0KYGBge3J9DQojIEZpbmRpbmcgdGhlIGltcG9ydGFuY2Ugb2YgaW5kZXBlbmRlbnQgdmFyaWFibGVzDQppbXBvcnRhbmNlKFJGbW9kKQ0KYGBgDQoNCmBgYHtyfQ0KI1Bsb3R0aW5nDQp2YXJJbXBQbG90KFJGbW9kKQ0KYGBgDQoNCg0KIyMjIFByZWRpY3QgdmFsdWVzIG9uIHRoZSB0ZXN0IHNldA0KYGBge3J9DQoNCiMgV2UgbmVlZCB0byBmaXJzdCBjb252ZXJ0IHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgdG8gZmFjdG9ycw0KDQp0ZXN0NCR3b3JrY2xhc3M8LWFzLmZhY3Rvcih0ZXN0NCR3b3JrY2xhc3MpDQp0ZXN0NCRzZXg8LWFzLmZhY3Rvcih0ZXN0NCRzZXgpDQp0ZXN0NCRvY2N1cGF0aW9uPC1hcy5mYWN0b3IodGVzdDQkb2NjdXBhdGlvbikNCnRlc3Q0JGVkdWNhdGlvbjwtYXMuZmFjdG9yKHRlc3Q0JGVkdWNhdGlvbikNCnRlc3Q0JFg8LWFzLmZhY3Rvcih0ZXN0NCRYKQ0KdGVzdDQkbWFyaXRhbC5zdGF0dXM8LWFzLmZhY3Rvcih0ZXN0NCRtYXJpdGFsLnN0YXR1cykNCg0KI1ByZWRpY3RpbmcNCg0KbGV2ZWxzKHRlc3Q0JHdvcmtjbGFzcykgPC0gbGV2ZWxzKHRyYWluNCR3b3JrY2xhc3MpDQpwcmVkNDwtcHJlZGljdChSRm1vZCxuZXdkYXRhID0gdGVzdDQsIHR5cGUgPSAiY2xhc3MiKQ0KDQpgYGANCg0KDQoNCiMjIyBCdWlsZCBhIGNvbmZ1c2lvbiBtYXRyaXggYW5kIGNhbGN1bGF0ZSB0aGUgYWNjdXJhY3kNCmBgYHtyfQ0KdGFibGUodGVzdDQkWCxwcmVkNCkNCmBgYA0KDQpgYGB7cn0NCmFjYzQ8LSg0MTM2KzgyMykvKDQxMzYrODIzKzY3OSszOTUpDQphY2M0DQpgYGANClx0ZXh0Y29sb3J7Ymx1ZX17SW4gdGhpcyBjYXNlLCB3ZSBhcmUgZ2V0dGluZyBhbiBhY2N1cmFjeSBvZiA4MiBwZXJjZW50YWdlLn0NCg==